Workbox: getting a new service worker while offline, delay between installin and waiting to activate - progressive-web-apps

I am developin a Progressive Web App and would like to make life as easy as posible for the users when a new service worker is received since the app needs to be reopen.
At first, using workbox-config.js I asked for running self.skipWaiting to be executed when the 'SKIP_WAITING' is received. This code was added in sw.js:
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
But that didn't work, the service worker was still waiting to be activated. The code is still in sw,js.
I google and found out some code I could use to display a message to the user asking him to close the app and restart it. I added this code in index.html
if ('serviceWorker' in navigator) {
window.addEventListener('load', async function() {
const registration = await navigator.serviceWorker.register('/sw.js');
if (registration.waiting && registration.active) {
alert('Fermer l\'application afin d\'appliquer la mise-a-jour');
} else {
// updatefound is also fired for the very first install
registration.addEventListener('updatefound', () => {
registration.installing.addEventListener('statechange', () => {
if (event.target.state === 'installed') {
if (registration.active) {
// If there's already an active SW, and skipWaiting() is not
// called in the SW, then the user needs to close all their
// tabs before they'll get updates
alert('Fermer l\'application afin d\'appliquer la mise-a-jour');
} else {
console.log('Content is cached for the first time!');
}
}
});
});
}
});
}
At first I thought that didn't work, because the alert was not displayed, but I was wrong. I made this test and found out there was a delay induced, see
Started the App
Unsing Dev Tools I checked Offline
Change something and generated a new service worker
Deployed the app again
I unchecked Offline
A service worker was trying to install
About 5 minutes later the service worker was installed and waiting
And then I got the alert.
What is the explanation for the 5 minutes delay ?
Thanks a lot
Gilles Plante

Your web app won't automatically check for an updated service worker unless one of the following conditions are met:
A navigation to an in-scope page.
A functional events such as push and sync, unless there's been an update check within the previous 24 hours.
Calling .register() only if the service worker URL has changed. However, you should avoid changing the worker URL.
In practice, unless you have a single-page app, most of your users will end up triggering a service worker update check each time they navigate to a new page. But if you have a single-page app that uses the History API instead of "real" navigations, it's possible the check won't happen as often as you'd like.
You can work around this by manually calling the update() method on your service worker registration:
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
});

Related

PWA home screen "uninstall" DOM event

I am trying to keep track of whether a web app has been installed to the user's home-screen using a value in localStorage.
I know there is DOM event that fires when a web app has been installed into a user's home-screen, but is there an event for when it has been uninstalled?
The type of event I have in mind would ideally be scheduled in a manner similar to (and behave in a manner similar to) onunload. (ie. an uncancellable event that allows me to schedule some last bit of work before the app is destroyed)
eg:
window.addEventListener('appinstalled', function(e) {
console.log('onappinstalled', e)
localStorage.setItem('APP_INSTALLED', '1')
})
// given the above, is anything like the following possible?
window.addEventListener('appuninstalled', function(e) {
console.log('onappuninstalled', e)
localStorage.setItem('APP_INSTALLED', '0')
})
I realised that once a user has uninstalled the app from their home-screen, the browser will begin prompting to install the app to the home-screen, again, provided you have met the criteria.
So by using the onbeforeinstallprompt event, there is an opportunity to clear the 'APP_INSTALLED' key from localStorage, and perform other arbitrary work.
eg:
window.addEventListener('beforeinstallprompt', function(e) {
localStorage.removeItem('APP_INSTALLED')
})
Moreover, this localStorage key may have already been cleared if the user elected to delete all data associated with the app when uninstalling the app from their home-screen.

Update Service Worker in Facebook Browser

Got a problem where some of our users have a buggy Service Worker sitting in their Facebook Browsers from our sites.
The problem: Facebook App users are getting our 'you are offline page' on the FB browser when they access our pages shared on FB.
The bug appeared to be that an old version of Google's Workbox (3.6.1) was automatically returning the 'You Are Offline' page in the FB app using Chrome 75. Updating Workbox fixed it.
The reference to Workbox was in the service worker, so when we updated our Workbox version (which fixed the issue) some users still had the old one cached.
If users clear their FB App caches or reinstall FB, then all's well and they can see our content. But we'd like to try to force the SW to update without asking them to do that.
To wipe the old Service Worker out from the FB browser, we tried the following:
<script>
"use strict";
console.log("Service Worker Registration");
function isFacebookApp() {
var ua = navigator.userAgent || navigator.vendor || window.opera;
return (ua.indexOf("FBAN") > -1) || (ua.indexOf("FBAV") > -1);
}
if ("serviceWorker" in navigator) {
if(isFacebookApp() == true) {
console.log("Service Worker Registration: using v2, via Facebook App");
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
console.log("Service Worker Registration: "+registration);
registration.unregister();
}
});
} else {
console.log("Service Worker Registration: using v2, not via Facebook App");
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js");
});
}
}
</script>
However our analytics show that we're still getting hits on the You Are Offline page, and we're still getting reports from users that they can's follow links to our articles.
Can anyone help? Is there a way of forcing FB's in-app browser to update the cache, and get our users using a working Service Worker?
Update
So far we have tried:
Fooling the browser to update the Service worker by using a query string in the SW registration URL
Iterating through the registrations and unregister()ing (see above)
Using skipWaiting() in the Service Worker code
Adding 'no-cache' to our headers on sw.js
Ensuring that sw.js isn't being cached by the server
Removing or changing the /offline page resulted in net::ERR_FAILED error
We suspected that all our service worker revisions may have some unseen fault in them, so we tried an 'empty' service worker to see if that would work and flush out the others
Our analytics indicate the following:
Chrome Mobile 75 may be an issue
problems seem to have occurred since 6/7 June.
Nothing's worked: our Service Worker, or something, is still returning the Offline page...
This is a bug in Chrome/the Chromium WebView, tracked in https://bugs.chromium.org/p/chromium/issues/detail?id=977784
The fix should be broadly rolled out at this point.
we experienced the same problem but presenting slightly differently - our users began receiving an error message "net:: ERR FAILED" though it sounds like the same cause. The problem began around 10 days ago, and the only way I've managed to get around the cache is to send the users to a new version of the site - by removing the www. from the URL that I'm sending to, and stopping this from 301'ing to with the www.
I also tried a bunch of solutions like you have, but it doesn't seem to be possible to get the service worker to contact the website - if you put Charles between your phone and the internet you'll see no traffic reaches your site. I'm hoping that after a period of disuse the service worker will be cleaned up by the app cache and we can switch back. Sorry I can't be more help, but rest assured you're not alone!

How to control if PWA uses cached version or fetches latest version?

I have installed my PWA from Chrome and Firefox on Android, and from Safari on iOS. When I update my code on the website, I see quite different behaviour in the PWAs in terms of using older cached versions vs the newest one - Firefox-created PWA seems to require about 2-3 kill and restarts of the PWA, Chrome takes 5-6, and I couldn't get Safari-based PWA to start showing the newest version without deleting the PWA and re-adding to Home Screen from browser.
Is there a spec that defines the conditions under which a newer, non-cached version is fetched? After much reading, I disabled the registering of my service worker, which should have made the PWAs network-only (no service-worker cache) but I continue to get old versions of the site served up in the PWAs. A normal browser window also seems to require multiple deep refreshes to get the new version, so I assume there is something besides the service worker that determines this?
Consider the following code.I will break it in parts:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
Over here cached first strategy is used,whenever you reload the page a fetch event is triggered.
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
First it checks if the required request is available in the cache,if yes then it will return the response and won't fetch from network.
return fetch(event.request).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
Now if the file was not present in the cache then this block gets to network gathers the required files then respond with it an also save it to cache for further use.
Consider the case that you have a file sample.html and it is cached,now you make some changes to the file's code but the changes won't be seen on your browser because it will see that the sample.html(old) was already present in the cache and respond with it.
Few generalized considerations regarding SW and PWAs:
Once you register your Service Worker (SW), you have to un-register it to make it sure you get the latest/updated version, and you can do so in the application tab under service worker
and then
to make sure you get the latest/update version of you PWA. What else you can do on top of it is, you can change the object name of the cache storage like below:
.
As long as you keep the object name same as well as don't unregister the SW and clear the Cache storage either, you will have to refresh your website. There is also, hard reload/clear cache and hard reload option as well if you keep pressing the refreshing button of the browser for couple of seconds but it still doesn't work until you unregister your service worker. So, in short unregistering SW and clearing Cache storage manually or changing the name of the Cache storage object will do the trick. Like any other technology/asset there are pros and cons. This is one of the draw back of the PWA if we don't use it properly, your client will never get the latest version, or the time he will get it may be too late. Cheers:)

PWA. How to make an offer to the user to install the application?

I can of course force install my pwa on the device. However, existing sites on the market themselves offer the user to install the application. And about the possibility to install my application, the user will not know if he does not want to try (most likely he will not want to).
How to make the user such an offer, I unfortunately have not yet figured out. Articles could not be found (perhaps incorrectly set the search), the analysis of the code of service workers also did not help.
Help please.
On Chrome mobile, the default prompt is very visible. On desktop, less so.
But, Chrome actually has an event for this. If everything is in order for a PWA to be installed, the 'beforeinstallprompt' event is fired. You can simply add a listener to this event, and use that to display a message on your page to inform the user of the possibility to install the PWA.
The following example is written for Angular, but you can get the idea of the event.
ngOnInit() {
/**
* The beforeinstallprompt event is only triggered in certain browsers. This event simply indicates that everything is in order
* for the user to install the PWA. On mobile Chrome, a message is shown by default to the user, but we can also interfere and
* block it. This way, we can show our own message, and continue the event on our own terms.
* In this case, we store the event, and prevent it from continuing. We then show a regular <div> in the HTML, which contains the
* question to install the PWA, and a button to do so. That button then triggers the prompt, which the user can then accept or deny.
* The result of this prompt is mostly irrelevant to the functionality. Our code has no impact on the proceedings of the installation
* after the user has accepted the prompt.
* A possible usecase for the Promise resolved by the prompt, is for metrics. We can use the result to calculate how many users have
* accepted or denied our prompts.
*/
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
this.deferredPrompt = e;
console.log('beforeinstallprompt!');
// if askedOnce is true, no need to ask again.
this.showPwaPrompt = !this.askedOnce;
});
}
acceptPwaPrompt() {
this.showPwaPrompt = false;
this.askedOnce = true;
this.deferredPrompt.prompt(); // Wait for the user to respond to the prompt
this.deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
this.deferredPrompt = null;
});
}

How to unregister and remove old service worker?

I have register a service woker using .register and can see in Application tab in dev tool. now my questionn is how to unregister this service worker.
I have run this script
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
console.group('====SW registration=======');
navigator.serviceWorker.register('/sw.js', {scope: './'}).then(function(registration) {
console.log('SW registratered successfully');
console.log(registration);
registration.unregister().then(function(result) {
console.log('Unregistered done', result);
});
}).catch(function(error){
console.error('sw registration failed', error);
});
console.groupEnd();
});
}
But I see that by doing this, we are actually first registering the service worker and then unregister it. This seems not the correct way to me.
Alternatively, I can click on unregister link near to the service worker from dev tools Application > Service Workers and also click on Application >Clear Storage and re-open the URL in the new tab.
but when I check through chrome://serviceworker-internals/
it displays the list of old and unregistered service worker at bottom of list ( see image)
So why I am seeing the redundant service worker list here? when will it update? does this is the default behaviour of chrome browser.
You should specify why you want to unregister the Service Worker.
I suppose there are two possibilities:
You want to get rid off the /service-worker.js file and remove SW completely
You want to have a new/updated SW and replace the previous one
First case:
Checkout this answer: How do I uninstall a Service Worker?
Second case:
You don't have to unregister the old SW, you just update the code in /service-worker.js and register it on top of the old one. This is the usual scenario. The new, updatet SW will takeover and the obsolete will go away.
I suggest you read these tutorials very carefully:
https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle
https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
I'm stressing carefully since it's very easy to get SW related stuff wrong and completely bork your website :)
May be my answer at below link will help you out to solve your query.
Stackoverflow link : Unregistering/Removing a Service Worker
LINK: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/unregister
If you want to update service worker code than use https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update
Below code will remove service worker(unregister it)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations()
.then(function(registrations) {
for(let registration of registrations) {
if(registration.active.scriptURL == 'http://localhost/my-push/myworker.js'){ registration.unregister(); }
}
});
}