Basically I am working with an online/offline app. I developped a custom hook to detect wether a user has connection or not. For this I am sending a random fetch request. However the service worker is intercepting the request and send a 200 even though the user is clearly offline. My question is, can I ingore a specific endpoint in the service worker ?
const checkOnline = async () => {
try {
const response = await fetch('/test.test');
setOnline(response.ok);
console.log('response.source', response.url)
} catch {
setOnline(false);
}
};
You can detect internet connection status via window.navigator.onLine. No need to make a fake request
Related
i'd like to ask some question about how to close a websocket client when offline/switched network.
when i try to close the socket for the 2 case in chrome, after i call websocket.close, i cannot recieve onclose event for a long time (around 60s), then i can recieve it finally.
after i check the readystate, i found that in the coming 60s, the state is 2(CLOSEING), not turned to 3(CLOSED).
so i'd like to know is there any steps i missed when i call websocket.close() in offline/switched network condition. while it runs well when the network is normal.
what's your back-end framework?
If you try to handle client's network that suddenly turned offline, there're two way you can try to close websocket from client as follows.
Kindly refer to source code here.
Using the js offline event handle
If we would like to detect if the user went offline we simply add websocket close function into offline event function.
front-end
function closeWebSocket() {
websocket.close();
}
$(window).on('beforeunload offline', event => {
closeWebSocket();
});
back-end (WebSocketServer)
#OnClose
public void onClose(Session session) {
CURRENT_CLIENTS.remove(session.getId());
}
Using Ping interval on the client-side and decrease the websocket timeout on server-side
If websocket server doesn't receive any message in specific time, it will lead to a timeout. So we can use this mechanism to decrease the timeout to close session if the client doesn't send any ping due to offline.
front-end
// send ping to server every 3 seconds
const keepAlive = function (timeout = 20000) {
if (websocket.readyState === websocket.OPEN) {
websocket.send('ping');
}
setTimeout(keepAlive, timeout);
};
back-end (WebSocketConfig)
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(5000L);
return container;
}
I am trying to add a parameter to the body of a POST request in a service worker but the original body is send. I use the following code
let token = '';
self.addEventListener('message', function (event) {
if (event.data && event.data.type === 'SET_TOKEN') {
token = event.data.token;
}
});
self.addEventListener('fetch', function (event) {
const destURL = new URL(event.request.url);
const headers = new Headers(event.request.headers);
if (token) headers.append('Authorization', token);
if (destURL.pathname === '/logout/') {
const promiseChain = event.request.json().then((originalBody) => {
return fetch(event.request.url, {
method: event.request.method,
headers,
// this body is not send to the server but only the original body
body: JSON.stringify(Object.assign(originalBody, { token })),
});
});
event.respondWith(promiseChain);
return;
}
const authReq = new Request(event.request, {
headers,
mode: 'cors',
});
event.respondWith(fetch(authReq));
});
Generally speaking, that should work. Here's a very similar live example that you can run and confirm:
https://glitch.com/edit/#!/materialistic-meadow-rowboat?path=sw.js%3A18%3A7
It will just POST to https://httpbin.org/#/Anything/post_anything, which will in turn echo back the request body and headers.
If your code isn't working, I would suggest using that basic sample as a starting point and slowing customizing it with your own logic. Additionally, it would be a good idea to confirm that your service worker is properly in control of the client page when its makes that request. Using Chrome DevTool's debugger interface, you should be able to put breakpoints in your service worker's fetch event handler and confirm that everything is running as expected.
Taking a step back, you should make sure that your web app isn't coded in such a way that it requires the service worker to be in control in order to go things like expire auth tokens. It's fine to have special logic in the service worker to account for auth, but make sure your code paths work similarly when the service worker doesn't intercept requests, as might be the case when a user force-reloads a web page by holding down the Shift key.
I have been working on a websocket client application.
I am currently using the ws client library, because it is easy to add some headers (I need this for authentication purposes). I have made a successful connection to the server, but now I need to connect to a specific channel.
Current code:
const WebSocket = require('ws');
var option = {
headers: {
api_key: 'xxxxx'
}
};
// I know this is not a correct url, but I removed it for security reasons
const url = '../websocket/';
const wss = new WebSocket(url, option);
wss.on('open', () => {
console.log("Connection is succesfull");
wss.on('message', message => {
console.log(message);
});
});
When I ran the code it prints the "Connection succesfull", but now I want to connect to a channel called /people. How can I do this.
I have tried several things like:
Changed the url websocket/people.
This doesn't work because it first needs to authenticate to user, before making a connection to a channel
Changed the url to websocket/?people.
I don't get an error, but I also don't get response back when something is send to this channel.
Add this in the open function:
wss.on('/people', message => {
console.log(message);
});
I don't get an error, but I also don't get response back when something is send to this channel.
For the record. I only have access to the documentation and not to the server.
I'm currently considering adding service workers to a Web app I'm building.
This app is, essentially, a collection manager. You can CRUD items of various types and they are usually tightly linked together (e.g. A hasMany B hasMany C).
sw-toolbox offers a toolbox.fastest handler which goes to the cache and then to the network (in 99% of the cases, cache will be faster), updating the cache in the background. What I'm wondering is how you can be notified that there's a new version of the page available. My intent is to show the cached version and, then, if the network fetch got a newer version, to suggest to the user to refresh the page in order to see the latest edits. I saw something in a YouTube video a while ago but the presenter gives no clue of how to deal with this.
Is that possible? Is there some event handler or promise that I could bind to the request so that I know when the newer version is retrieved? I would then post a message to the page to show a notification.
If not, I know I can use toolbox.networkFirst along with a reasonable timeout to make the pages available even on Lie-Fi, but it's not as good.
I just stumbled accross the Mozilla Service Worker Cookbooj, which includes more or less what I wanted: https://serviceworke.rs/strategy-cache-update-and-refresh.html
Here are the relevant parts (not my code: copied here for convenience).
Fetch methods for the worker
// On fetch, use cache but update the entry with the latest contents from the server.
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.');
// You can use respondWith() to answer ASAP…
evt.respondWith(fromCache(evt.request));
// ...and waitUntil() to prevent the worker to be killed until the cache is updated.
evt.waitUntil(
update(evt.request)
// Finally, send a message to the client to inform it about the resource is up to date.
.then(refresh)
);
});
// Open the cache where the assets were stored and search for the requested resource. Notice that in case of no matching, the promise still resolves but it does with undefined as value.
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request);
});
}
// Update consists in opening the cache, performing a network request and storing the new response data.
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response.clone()).then(function () {
return response;
});
});
});
}
// Sends a message to the clients.
function refresh(response) {
return self.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
// Encode which resource has been updated. By including the ETag the client can check if the content has changed.
var message = {
type: 'refresh',
url: response.url,
// Notice not all servers return the ETag header. If this is not provided you should use other cache headers or rely on your own means to check if the content has changed.
eTag: response.headers.get('ETag')
};
// Tell the client about the update.
client.postMessage(JSON.stringify(message));
});
});
}
Handling of the "resource was updated" message
navigator.serviceWorker.onmessage = function (evt) {
var message = JSON.parse(evt.data);
var isRefresh = message.type === 'refresh';
var isAsset = message.url.includes('asset');
var lastETag = localStorage.currentETag;
// ETag header usually contains the hash of the resource so it is a very effective way of check for fresh content.
var isNew = lastETag !== message.eTag;
if (isRefresh && isAsset && isNew) {
// Escape the first time (when there is no ETag yet)
if (lastETag) {
// Inform the user about the update.
notice.hidden = false;
}
//For teaching purposes, although this information is in the offline cache and it could be retrieved from the service worker, keeping track of the header in the localStorage keeps the implementation simple.
localStorage.currentETag = message.eTag;
}
};
I want to connect to a remote server and use that for logins. This was not particularly hard.
Remote = DDP.connect('http://somesite.com');
Accounts.connection = Remote;
Meteor.users = new Mongo.Collection('users', Remote);
However, when I call meteor methods on my local code (there are multiple servers, but one login), it does not recognize the user.
Meteor.methods({
'start': function () {
if (!this.userId) {
// ...
} else {
throw new Meteor.Error(401, 'Unauthorized');
}
}
});
This always results in an error, despite being logged in.
How can I set my local user to the same user as the remote user?
Let's rename the following:
Remote => Login Server
local => Default Server This is where you call the Methods
I think you are better served by logging into Default Server, which then relays the login attempt to the Login Server.
This way you will be logged in on Default Server (if Login Server confirms the credentials are valid) when you use the Meteor.Methods that are on Default Server.
Accounts.validateLoginAttempt allows you to run arbitrary code in a callback on a LoginAttempt, allowing you to pass the validation from Default Server to Login Server:
if (Meteor.isServer) {
Accounts.validateLoginAttempt(function(attempt) {
//psuedocode block
var res = LoginServer_Login(attempt.methodArguements)
if (res === true) return true; // Login Success
else return false; // Login Failed
});
}
I'm not sure of the best way to implement LoginServer_Login func, though I'd try using HTTP.post to communicate with Login Server first (and recommend using restivus on the Login Server, it will give you authentication routes out of the box).
I just came across this package: admithub:shared-auth
It requires a shared db between the two meteor apps though.
Beyond this, you probably need to look into full SSO solutions.