I'm trying to make periodic bg sync where service worker updates badge.
When I run my page and test it via Chrome DevTools, Service worker process the request. But when the page is closed, it doesnt't do anything. Same on mobile phone.
On my page (this part is working and output in console is periodic update set):
navigator.permissions.query({name:'periodic-background-sync'}).then(function(result) {
if (result.state === 'granted') {
console.log('periodic background granted');
navigator.serviceWorker.ready.then(function(registration){
if ('periodicSync' in registration) {
try {
registration.periodicSync.register('update-badge', {
minInterval: 60 * 60 * 1000,
}).then(function(ret){
console.log('periodic update set');
});
} catch (error) {
console.log('Periodic background sync cannot be used.');
}
}
});
}
});
Service worker:
async function updateBadge() {
const unreadCount = 5; //fixed value for testing
navigator.setAppBadge(unreadCount).catch((error) => {
console.log('Error setting badge.');
});
}
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'update-badge') {
event.waitUntil(updateBadge());
}
});
So when I manually fire background sync from DevTools, badge is set, but not automatically in the background as I thought it will work.
I can't find anything wrong with the code. I've seen variants where you request periodic-background-sync after navigator.serviceWorker.ready but I think that both work (especially since you can trigger the registered event manually).
I think the problem is that not all conditions for periodic background sync to fire are true. These are the conditions from the initial implementation in Chrome (2019):
Chrome version 80 or later
The PWA needs to be installed
The website needs to have an engagement score (can be viewed here chrome://site-engagement/). Websites with 0 engagement is deleted from the list and won't trigger the periodicsync event, even if it is installed. Once a user hasn’t interacted with a website for 2 hours, its engagement levels begin to decay. With minimal engagement, a periodicsync event will be triggered at an interval of 36h. source
In your case I think your code is correct and working but you haven't interacted enough with you app continuously over 36h so the engagement is purged and periodicsync fire timer cancelled (if you have installed your PWA).
For the record, here is a complete working demo (event registration) (and sw code).
The specific interval at which the periodicsync event is fired varies; what I've seen on, e.g., installed Android web apps is that you'll get periodicsync around once a day. It may be different on desktop platforms (and will only happen if your browser is actually running at the time).
Have you waited a full day or so after installing your PWA, and see if it's fired?
Related
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
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();
})
);
});
A pretty major problem for my app is that roughly 50% of launches have no Firebase connection unless the app is paused. So I would:
Launch the app to have no data loaded
Pause it to load the data on the current page
Resume it to see the data.
Repeat step 2 and 3 each time I need data sent or received
code:
var onPause = function(){console.log("paused");}
var onResume = function(){console.log("resumed");}
document.addEventListener("pause", onPause, false);
document.addEventListener("resume", onResume, false);
var connectionRef = new Firebase(FB + "/.info/connected");
connectionRef.on("value", function(snap){
if(snap.val() == true){
console.log("connected -------[]------");
} else {
console.log("not connected --------[ ]------------");
}
});
logs:
resumed
not connected --------[ ]------------
paused
connected -------[]------
resumed
not connected --------[ ]------------
paused
connected -------[]------
The reason it works half the time is because it also works the opposite way, which I assume is the intended way. Is there any way to prevent it from disconnected at all? or alternatively force it to connect on resume?
I found the cause to the problem - It was a plugin called phonegap-plugin-push. When it registers for GCM it causes Firebase to go offline (or online if paused).
I still haven't found a genuine solution, but as a hack fix I have a timer on registering for GCM so the data can load initially before the disconnecting happens. After that it relies on the user not using the app for extended periods of time so it can pause to sync up on a regular basis.
i am writing an offline web application.
i have a manifest files for my applicationCache. and i have handlers for appCache events.
i want to detect if the files being downloaded are being downloaded for the first time or being updated. because in case they are being updated, i would like prevent my application code from running, since i will refresh the browser after updating my app.
my specific problem here is that when the "checking" events gets fired, the applicationCache status is already "DOWNLOADING",
does anybody know how to get the applicationCache.status before any manifest or files gets downloaded?
thanks in advance for any answer
window.applicationCache.addEventListener('checking', function (event) {
console.log("Checking for updates.");
console.log("inside checking event, appcache status : %s", applicationCache.status);
}, false);
window.applicationCache.addEventListener('updateready', function (e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new version of manifest files
window.location.reload();
}
}, false);
window.applicationCache.addEventListener('downloading', function (event) {
appCommon.information("New version available", "Updating application files...", null, null);
}, false);
window.applicationCache.addEventListener('progress', function (event) {
$("#informationModal").find(".modal-body").html("Updating application files... " + event.loaded.toString() + " of " + event.total.toString());
}, false);
window.applicationCache.addEventListener('cached', function (event) {
$("#informationModal").find(".modal-body").html("Application up to date.");
setTimeout(function () { $("#informationModal").find(".close").click(); }, 1000);
}, false);
According to the specification the checking event is always the first event.
the user agent is checking for an update, or attempting to download the manifest for the first time. This is always the first event in the sequence.
I think it is a mistake to refresh the browser since that will not automatically fetch a new copy. If you look at the MDN guide for using the applicationCache you'll see that files are always loaded from the applicationCache first and then proceed to go through the cycle of events you attempted to avoid by refreshing.
Instead of refreshing you should simply make proper use of the applicationCache event life cycle in order to initialize and start your application.
i would like prevent my application code from running, since i will refresh the browser after updating my app.
You have a lot of control of when your application begins to run, if you really wanted to refresh the browser, just always start the app after updateready or noupdate events, but really instead of refreshing it seems like you should use window.applicationCache.swapCache instead.
I found this question and answer useful.
Morning all, I'm looking for some kind of Javascript event I can use to detect when a mobile browser window regains focus, after either a user closes/minimizes their browser (to go back to a home screen/different app), or if the device resumes from sleep (either the user powering it off, or it going to sleep after a screen timeout).
I'd like to be able to find a single event that works for everything, but I know that's unlikely! The pageshow event works for iOS devices, but it's rather sketchy for use with everything else. I've tried focus and DOMActivate but neither of them seem to have the desired effect.
The page may not always have form elements on it, and I don't really want the user to have to touch the page again to trigger the event.
The requirement for such an event is caused by our code periodically checking for new content by making XHR requests. These are never sent when the browser is asleep, so we never get new content to restart the timeouts.
Thanks for any help you guys may be able to provide!
We had a similar issue and solved it something like this:
var lastSync = 0;
var syncInterval = 60000; //sync every minute
function syncPage() {
lastSync = new Date().getTime(); //set last sync to be now
updatePage(); //do your stuff
}
setInterval(function() {
var now = new Date().getTime();
if ((now - lastSync) > syncInterval ) {
syncPage();
}
}, 5000); //check every 5 seconds whether a minute has passed since last sync
This way you would sync every minute if your page is active, and if you put your browser in idle mode for over a minute, at most 5 seconds will pass before you sync upon opening the browser again. Short intervals might drain the battery more than you would like, so keep that in mind when adapting the timings to you needs.
Better than an interval would be to add a window blur listener and a window focus listener. On blur, record current time. On focus, validate you are still logged in / sync'd / whatever you need to do.
Basically exactly the same thing but it runs only when necessary rather than slowing your entire page down with an interval.
Update
var $window = $(window),
$window.__INACTIVITY_THRESHOLD = 60000;
$window.add(document.body); //necessary for mobile browsers
$window.declareActivity = function () { $window.__lastEvent = new Date(); };
$window.blur($window.declareActivity);
$window.focus(function(){
var diff = (new Date()) - $window.__lastEvent;
if (diff > $window.__INACTIVITY_THRESHOLD) {
$window.trigger("inactivity");
}
});
$window.on("inactivity", "", null, function () {
//your inactivity code
});
Though that blur event seems sketchy if the phone is powering off and I don't know that I would trust it in all circumstances / mobile devices. So I'd probably throw in something like this:
$(document.body).on("click scroll keyup", "", null, $window.declareActivity);
so that my inactivity timer works for when the user just walks away as well. Depending on your site, you may want to adjust that exact event list - or simply throw in a $window.declareActivity(); into your existing scripts that respond to user inputs.