Service Worker: Inactive Event Listener - javascript

Does anybody know if there's an event listener which detects when a service worker is about to become inactive? Something like:
self.addEventListener('inactive', (event) =>
{
// Random piece of code here
}
Reason being is that I want to save data to storage before it becomes inactive since the data gets removed once it becomes inactive.

Related

How to emit an event on application close using socket.io

I am trying emit an event while user closes the tab and for this I am emitting an event on unmount of a component ( which is nested inside a Main component) :
Emitting an event REMOVE_SOCKETS inside the cleanup function of useEffect unfortuantley doesn't trigger the event listener on the server side. I want to pass some paramertes like url, uid to know at which page/path the user closed the tab.
useEffect(() => {
return () => {
debugger;
if (socket) {
socket.emit('REMOVE_SOCKETS', {
url: window.location.href,
});
socket.disconnect();
}
};
}, []);
on express server side:
s.on('REMOVE_SOCKETS', () => {
// doing some application logic
})
I kept debugger statement to check if the dev tools in Chrome browser pauses instead of the closing of tab while unmounting the component but that didn't happen.
I searched in various answers of the similar question that to emit an event inside the disconnect event listner on server side as soon as we detect disconnect event like the following :
s.on('disconnect', () => {
s.emit('DISCONNECT_ACK_TO_CLIENT') // which is not needed in my case
} )
but in my case the client will no longer be available and also there is nothing I want to do by emitting event inside disconnect.
Also is there a way I can differetiate tab close and refresh, so that I can emit REMOVE_SOCEKTS only on tab close not on tab refresh ?
As far as I can tell, it is not possible to do much when the tab is being closed by the user. And this is on purpose. Otherwise, the user won't be able to close malicious tabs. Event if that was possible, it would still not be reliable - consider killing the browser using task manager for example.
One common solution would be to listen for the "close" event on the server side, and do whatever is needed. This should be complemented with ping/pong messages, in case the socket dies unexpectedly, to track the connection state.

Misunderstanding concept of localStorage

I'm using this code published by Sasi Varunan here
<script type="text/javascript">
// Broad cast that your're opening a page.
localStorage.openpages = Date.now();
var onLocalStorageEvent = function(e){
if(e.key == "openpages"){
// Listen if anybody else opening the same page!
localStorage.page_available = Date.now();
}
if(e.key == "page_available"){
alert("One more page already open");
}
};
window.addEventListener('storage', onLocalStorageEvent, false);
</script>
Code is working like charm - doing exactly what I want - detect if an application it's already open in another tab browser or even in another browser window.
From my understanding:
The very first time when the application is started the followings thinks happen:
App set openpages key with Date.now() value.
Because of 1. storage event listener start onLocalStorageEvent event.
Because the openpages key exists, is setting page_available key with Date.now ()
When the followings apps are started they find page_available key in storage (the second if) the alert is triggered and I can redirect them to an error page.
QUESTION:
If I close all the browser windows and restart the app in a new winwdow everything still works fine.
This is what I don't understand because the page_available key is persistent is still there in the storage (no one deleted) the app should go on the second if and that the alert ... but this is not happening.
The very first time when the application is started the followings thinks happen:
App set openpages key with Date.now() value.
Because of 1. storage event listener start onLocalStorageEvent event.
Because the openpages key exists, is setting page_available key with Date.now ()
When the followings apps are started they find page_available key in storage (the second if) the alert is triggered and I can redirect them to an error page.
The entire idea here is to communicate between the tabs using the storage event that is being triggered every time you access localStorage.
So when the page loads it first access the openpages key to trigger the storage event with e.key == "openpages".
Only after that it registers the event listener. So 2 only happens when you load the page on a second tab. On the second tab the event is triggered and the event listener is registered. Because the storage event is triggered for all the tabs, the event listener of the first tab (which is already registered) is being executed.
Tab 1 is triggered by the storage event with e.key == "openpages" and gets into the first if. There it triggers the storage event by accessing page_available key.
At this point tab 2 event listener reacts to the storage event but this time with e.key == "page_available" and gets into the second if where it shows the alert.
Note that if you don't close the tabs and open more tabs, tab 3 will trigger the storage event for all other tabs and you will have multiple tabs with alerts.
Just for reference:
If you want to trigger the alert on the first tab and not the second one you can achieve it with this code:
// Broadcast that you're opening the page.
localStorage.openpage = Date.now();
var onLocalStorageEvent = function(e) {
if (e.key == "openpage") {
alert("One more page already open");
}
};
window.addEventListener('storage', onLocalStorageEvent);
Read more about localStorage here.
Read more about addEventListener here.
After a restart of the browser window everything still works fine, and
I don't understand why because the page_available key is still there
in the storage
This is because localStorage has no expiration date which is opposite of sessionStorage. sessionStorage gets cleared once the browser is closed, but localStorage still remains.
You can still clear the localStorage by clearing the browser cache & cookies
Also this snippet localStorage.openpages = Date.now(); seems to be incorrect.
If you want to set a value in localStorage, do like this
localStorage.setItem('openpages',Date.now());

Are there any window events triggered if user "pulls the plug" and shuts down their computer?

I have a website, and I only want the client to be able to have 1 WebSocket connection at a time (when they open another tab while there is already another connection display, I display an error to them).
I'm working on a client-side solution where I update a flag in local storage to true when the connection is requested (It won't request if the flag is already true) then I listen for the beforeunload event and set the local storage flag to false if that tab had an open connection.
This seems to be working great except for the edge case of when a user shuts down their computer abruptly and thus beforeunload never fires, so when they turn their computer back on the local storage flag is stuck at true and they are stuck not being able to connect in any tabs.
Is there an event that will be called before the shutdown where I can set my local storage flag to false?
If not is there another solution for the client to keep track that it has only 1 WebSocket connection across all tabs so it can block a connection if there is already one?
window.addEventListener('beforeunload', this.setFlagToFalse);
As correctly stated in Jaromanda's comment, a computer without power can not emit an Event to the browser (which doesn't even exist anymore...).
However, one solution to your root problem is to listen to the storage event.
This event will fire across all the Windows that do share the same Storage area, when an other Window will make any modification to this Storage.
So we can use it as a mean to communicate between Windows from the same domain, in almost real time. This means that you don't have to keep your flag up to date, you can now know directly if an other Window is already active.
Here is a basic implementation. I'll let you the joy of making it more suited to your needs.
let alone = true; // a flag to know if we are alone
onstorage = e => { // listen to the storage event
if(e.key === 'am_I_alone') {
if(e.newValue === 'just checking') { // someone else is asking for permission
localStorage.am_I_alone = 'false'; // refuse
}
else if(e.newValue === 'false') { // we've been refused access
alone = false;
}
}
};
localStorage.am_I_alone = 'just checking'; // trigger the event on the other Windows
setTimeout(()=>{ // let them a little time to answer
if(alone) { // no response, we're good to go
// so the next one can trigger the event
localStorage.am_I_alone = "true";
startWebSocket();
}
else { // we've been rejected...
error();
}
}, 500);
Live Plnkr

How: ServiceWorker check if ready to update

What I am trying to achieve:
render page with loader/spinner
if service-worker.js is registered and active, then check for updates
if no updates, then remove loader
if updatefound and new version installed, then reload the page
else register service-worker.js
when updatefound, meaning new one was installed, remove loader
I am using sw-precache module for me to generate service-worker.js and following registration code:
window.addEventListener('load', function() {
// show loader
addLoader();
navigator.serviceWorker.register('service-worker.js')
.then(function(swRegistration) {
// react to changes in `service-worker.js`
swRegistration.onupdatefound = function() {
var installingWorker = swRegistration.installing;
installingWorker.onstatechange = function() {
if(installingWorker.state === 'installed' && navigator.serviceWorker.controller){
// updated content installed
window.location.reload();
} else if (installingWorker.state === 'installed'){
// new sw registered and content cached
removeLoader();
}
};
}
if(swRegistration.active){
// here I know that `service-worker.js` was already installed
// but not sure it there are changes
// If there are no changes it is the last thing I can check
// AFAIK no events are fired afterwards
}
})
.catch(function(e) {
console.error('Error during service worker registration:', e);
});
});
After reading the spec it is clear that there are no handlers for something like updatenotfound. Looks like serviceWorker.register checks if service-worker.js changed internally by running get-newest-worker-algorithm, but I cannot see similar methods exposed via public api.
I think my options are:
wait for couple of seconds after service worker registration becomes active to see if onupdatefound is fired
fire custom events from service-worker.js code if cache was not updated
Any other suggestions?
Edit:
I've came up with some code which solves this issue by using postMessage() between SW registration and SW client (as #pate suggested)
Following demo tries to achieve checks through postMessage between SW client and SW registration, but fails as SW code is already cached DEMO
Edit:
So by now it looks like I cannot implement what I want because:
when service worker is active you cannot check for updates by evaluating some code in SW - this is still the same cached SW, no changes there
you need to wait for onupdatefound, there is nothing else that will notify of changes in SW
activation of older SW comes before onupdatefound
if there is no change, nothing fires after activation
SW registration update() is immature, keeps changing, Starting with Chrome 46, update() returns a promise that resolves with 'undefined' if the operation completed successfully or there was no update
setting timeout to postpone view rendering is suboptimal as there is no clear answer to how long should it be set to, it depends on SW size as well
The other answer, provided by Fabio, doesn't work. The Service Worker script has no access to the DOM. It's not possible to remove anything from the DOM or, for instance, manipulate any data that is handling DOM elements from inside the Service Worker. That script runs separately with no shared state.
What you can do, though, is send messages between the actual page-running JS and the Service Worker. I'm not sure if this is the best possible way to do what the OP is asking but can be used to achieve it.
Register an onmessage handler on the page
Send a message from the SW's activate or install event to the page
Act accordingly when the message is received on the page
I have myself kept SW version number in a variable inside the SW. My SW has then posted that version number to the page and the page has stored it into the localStorage of the browser. The next time the page is loaded SW posts it current version number to the page and the onmessage handler compares it to the currently stored version number. If they are not the same, then the SW has been updated to some version number that was included in the mssage. After that I've updated the localStorage copy of the version number and done this and that.
This flow could also be done in the other way around: send a message from the page to the SW and let SW answer something back, then act accordingly.
I hope I was able to explain my thoughts clearly :)
The only way that pops up in my mind is to start the loader normally and then remove it in the service-worker, in the install function. I will give this a try, in your service-worker.js:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(filesToCache);
}).then(function() {
***removeLoader();***
return self.skipWaiting();
})
);
});

Synchronizing sync and local chrome.storage

I would like to know how to handle both local and sync storage in the right way in Chrome extension please.
This is my case:
I'm working on an extension for only a specific site (for now),
which contains a content-script and a popup.
The popup contains options where the user can make changes, then the values are sent to the content-script to show the changes on the page.
I'm looking to make as less saving and retrieving storage tasks as possible, and that in the end it will get saved in the sync storage and not just in local.
The sync storage got a per-minute limit, where the local one doesn't.
I know how to listen to the popup closed call from the content-script using a long-lived connection and listen to the onConnect and onDisconnect, and then I can do a save task, but is there a better way to save reading and writing to the storage?
All I can think of was having a background script where I can store the changes in variables and then just send them back and forward to and from the content-script and popup, so it's like having a storage without actually using the storage, but then how can I detect when the user leaves the specific domain and then do the single saving task, and also close/stop the background/event script?
The current limit on chrome.storage.sync sustained operations is 1 every 2 seconds (more accurately 1800 per hour), and a burst rate limit of 120 per minute.
So, your job is to ensure sync happens no more often than once per 2 seconds.
I would make an event page that deals with chrome.storage.onChanged event and syncs the two areas. Which is a surprisingly hard task due to local echo!
// event.js, goes into background.scripts in manifest
// Those will not persist if event page is unloaded
var timeout;
var queuedChanges = {};
var syncStamp = 1;
chrome.storage.onChanged.addListener(function(changes, area) {
// Check if it's an echo of our changes
if(changes._syncStamp && changes._syncStamp.newValue == syncStamp) {
return;
}
if(area == "local") {
// Change in local storage: queue a flush to sync
// Reset timeout
if(timeout) { clearTimeout(timeout); }
// Merge changes with already queued ones
for(var key in changes) {
// Just overwrite old change; we don't care about last newValue
queuedChanges[key] = changes[key];
}
// Schedule flush
timeout = setTimeout(flushToSync, 3000);
} else {
// Change in sync storage: copy to local
if(changes._syncStamp && changes._syncStamp.newValue) {
// Ignore those changes when they echo as local
syncStamp = changes._syncStamp.newValue;
}
commitChanges(changes, chrome.storage.local);
}
});
function flushToSync() {
// Be mindful of what gets synced: there are also size quotas
// If needed, filter queuedChanges here
// Generate a new sync stamp
// With random instead of sequential, there's a really tiny chance
// changes will be ignored, but no chance of stamp overflow
syncStamp = Math.random();
queuedChanges._syncStamp = {newValue: syncStamp};
// Process queue for committing
commitChanges(queuedChanges, chrome.storage.sync);
// Reset queue
queuedChanges = {};
timeout = undefined;
}
function commitChanges(changes, storage) {
var setData = {};
for(var key in changes) {
setData[key] = changes[key].newValue;
}
storage.set(setData, function() {
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
}
});
}
The idea here is to sync 3 seconds after the last change to local. Each new change is added to the queue and resets the countdown. And while Chrome normally does not honor DOM timers in event pages, 3 seconds is short enough to complete before the page is shut down.
Also, note that updating an area from this code will fire the event again. This is considered a bug (compare with window.onstorage not firing for changes within current document), but meanwhile I added the _syncStamp property. It is used to distinguish the local echo, though there is a tiny chance that the stamp will result in a collision
Your other code (content script) should probably also rely on onChanged event instead of a custom "okay, I changed a value!" message.

Categories

Resources