I'm trying to get a basic service worker-based updating capability going for my web app.
(You'll see in the code below that I clear the cache by specific name - 4.7 - and by dynamic name. This is because it seems to me that the code I'm using clears the NEW cache name, not the old one. Maybe a separate issue. Right now I'm clearing both.)
If I change the cacheName and the annotateAccount.js file (this running on localServer) I can see that the service worker does its job: downloads a new version from the server (I see a status of "200 OK" in the console vs. "200 OK (from service worker)" when running a page refresh without an update to the service worker file.
Even though the code downloads a new version of the JS, my actual app (running in Chrome) pulls from an old cached version of the file.
If I "empty cache and hard reload" I get the new version. If I immediately do a regular refresh I get the old (browser cached) version of the file.
I've read enough to know there is browser caching on top of service worker caching, but what's the F-ing point of using a service worker if you can't overcome the browser's caching? I'm using XAMMP/Apache to run my localhost dev environment. I haven't tried this in a staging or production environment on my actual servers. Since I seem to be fighting Chrome's local caching features I'm not sure that matters.
I'd previously just put "?v=xxx" after filenames throughout my app, and changed the parameter when I released updates, in an attempt to give users the latest code. This doesn't work well.
What am I missing? Any direction/help appreciated.
Here's my service worker JS file
var urlsToCache = [
'annotateAccount.html',
'annotateAccount.js',
'web_background.js'
];
const cacheName = '4.8';
self.addEventListener('install', event => {
console.log('Install event...', urlsToCache);
event.waitUntil(
caches.open(cacheName)
.then(function(cache) {
console.log('Opened cache', cacheName);
return cache.addAll(urlsToCache);
})
);
});
var url;
self.addEventListener('fetch', (event) => {
//Cache falling back to network
url = event.request.url.split(".").pop(); // check php
if (url !== "php"){
event.respondWith(
caches.match(event.request)
.then((response) => {
return response || fetch(event.request);
})
);
} else { // END check for PHP - skip trying cache/SW.
console.log('Trying a php file - always let code run it...');
}
});
self.addEventListener('message', function (event) {
console.log('ServiceWorker cache version: ', cacheName, event);
console.log('Received msg1: ', event.data.action);
if (event.data.action === 'skipWaiting') {
console.log('ccClearing cache: ', cacheName);
caches.delete(cacheName); // actually removes cached versions
caches.delete("4.7"); // delete NAMED cache...
self.skipWaiting();
} else {
console.log('Do not clear cache...');
}
});
What I'd like to achieve is the ability to push a notification to the user when an update is available to my React webpage, and I am trying to do so via serviceWorkers.
The challenge I am facing is to get the installation property of the serviceWorkerRegistration interface to not return null. I just want to trigger
console.log('New content is available; please refresh.')
I am quite new to working with serviceWorkers in React. Does anyone have an idea on how to go about testing the update functionality of serviceWorkers in a development environment?
console.log('I am in registerValidSW!')
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
console.log(registration)
registration.onupdatefound = () => {
const installingWorker = registration.installing //This returns null
if (installingWorker) {
console.log('installingServiceWorker!!!')
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a 'New content is
// available; please refresh.' message in your web app.
console.log('New content is available; please refresh.')
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// 'Content is cached for offline use.' message.
console.log('Content is cached for offline use.')
}
}
}
}
}
})
.catch((error) => {
console.error('Error during service worker registration:', error)
})
#krish according to new documentation-> The service worker is only enabled in the production environment, e.g. the output of npm run build. It's recommended that you do not enable an offline-first service worker in a development environment, as it can lead to frustration when previously cached assets are used and do not include the latest changes you've made locally. refer this link :- https://create-react-app.dev/docs/making-a-progressive-web-app/
I'm developing a reactjs based application. I also made service-worker settings on it. After add to home screen , application never checks the server for new updates.
I also tried:
window.location.reload(true);
But it doesn't update new version.
I'm using Apache server to serve build folder and for update I'm getting a new build of my project and serve that on Apache server.
I finally resolved my problem after two days. The problem was in service-worker file. I had to add event listener if page reloaded and server files had changes so it will update the files.
So I added this section to serviceWorker.js in register function:
window.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
Just don't forget. This listener call when page is reload. So I make API service to check there is new version or not. if there is new version , It have to reload the page to get new files.
this question was so helpful: How to clear cache of service worker?
Update (December.1.2019):
I found better way to update new PWA. Actually that way (above) not work on iOS 13. So I decide check update by API. PWA Send current version to API and if there is new version released , in PWA we should delete all caches:
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
And after that reload application:
window.location.href = "./";
After reload because there is no cache to load pages on offline mode, so PWA will check server and get new version.
this work for me:
src/index.tsx
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
serviceWorkerRegistration.register({
onUpdate: (e) => {
const { waiting: { postMessage = null } = {} as any, update } = e || {};
if (postMessage) {
postMessage({ type: 'SKIP_WAITING' });
}
update().then(() => {
window.location.reload();
});
},
});
Create-react-app comes with a registerServiceWorker.js file that contains code to register a service worker. I'm just a bit confused as to how it works. The code in question is:
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
What needs to happen for that first console log, the one that displays "New content is available; please refresh," to display?
More specifically, how can I trigger this code to run when index.html changes (in the event that a script filename changes).
Let's break it down step by step.
navigator.serviceWorker.register Promise is resolved when the valid Service Worker existence has been established
registration.onupdatefound registers a listener for an event that is fired when the HTTP request for Service Worker has been resolved to some other file than previously (or when the SW has been found for the first time)
registration.installing.onstatechange registers a listener for the new Service Worker's lifecycle changes (from installing to installed, from installed to activating etc.)
if (installingWorker.state === 'installed') filters out all the states other than installed - so its positive branch will be executed after each new SW has been installed
if (navigator.serviceWorker.controller) checks if the page is currently controlled by any (previous) Service Worker. If true then we're handling the aforementioned update scenario here.
So summing up - this console.log will execute after the updated (not the first one) Service Worker has been correctly installed.
It will not be triggered after index.html change. It's only Service Worker code (pointed to by serviceWorker.register method) that is checked against. Note also that normally browsers (or Chrome at least?) do not check for the new SW version for 24h after the current one was downloaded. Note also that plain old HTTP cache set for the Service Worker file might mess up here if it was send with too aggresive cache header.
I am using a service worker to cache the JS and CSS files of a web app.
I mostly use the code from a Google Chrome example, the only thing I added is a notification and countdown that refreshes the window when "new or updated content is available".
The Google example: https://github.com/GoogleChrome/sw-precache/blob/5699e5d049235ef0f668e8e2aa3bf2646ba3872f/demo/app/js/service-worker-registration.js
However, this happens far too often and I don't understand why.
There has definitely not been any changes to the files on the server, but still sometimes I see the notification and the window reloads.
I expected this would only happen when any of the files governed by the service worker actually changes (they are all hashed via Webpack and have definitely not changed in between).
This is the code I use, inlined in index.html:
/* eslint-env browser */
'use strict';
function reloadApp(delay) {
var t = delay || 0;
var message = 'New or updated content is available. Reloading app in {s} seconds';
var getMessage = function() { return message.replace('{s}', t) }
var div = document.createElement('div');
div.id = 'update-notification';
div.innerHTML = getMessage();
document.body.appendChild(div);
var intervalID = setInterval(function() {
t = t - 1;
if (t <= 0) {
clearInterval(intervalID);
window.location.reload();
}
else {
div.innerHTML = getMessage();
}
}, 1000);
}
if ('serviceWorker' in navigator && window.location.protocol === 'https:') {
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
console.info('serviceWorker registered');
reg.onupdatefound = function() {
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
reloadApp(5);
} else {
console.log('Content is now available offline!');
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
}
This is the change I made:
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and the fresh content will
// have been added to the cache.
// It's the perfect time to display a "New content is available; please refresh."
// message in the page's interface.
console.log('New or updated content is available.');
} else {
became:
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
reloadApp(5);
The service worker itself is generated via sw-precache-webpack-plugin and the hashed files look like this:
var precacheConfig = [["cms.CrudFactory.144344a2.js","6a65526f764f3caa4b8c6e0b84c1b31b"],["cms.routes.c20796b4.js","f8018476ceffa8b8f6ec00b297a6492d"],["common.cms-main.0f2db9ff.js","92017e838aff992e9f47f721cb07b6f0"],["common.licensing-main.8000b17d.js","0d43abd063567d5edaccdcf9c0e4c362"],["common.mediaplayer-main.314be5d2.js","2061501465a56e82d968d7998b9ac364"],["common.ordering-main.783e8605.js","0531c4a90a0fa9ea63fbe1f82e86f8c6"],["common.shared-main.0224b0ea.js","956ae4d2ddbddb09fb51804c09c83b22"],["common.stores-main.98246b60.js","cbdc46bc3abeac89f37e8f64b1737a22"],["component.App.284fec19.js","07f1923f1a0098bf9eba28fc7b307c18"],["component.AppToolbar.00e371de.js","9b542d4a85bdeece9d36ee4e389f6206"],["component.DevToolbar.652bf856.js","1744126e32774a93796146ac878ddd8e"],["component.Grid.4b964e52.js","755819ca2c7f911805e372401d638351"],["component.MediaPlayer.54db6e85.js","d0d8ae269665e810d1b997b473005f76"],["component.Search.05476f89.js","0cae8928aff533a6726dfaeb0661456a"],["data.country-list.d54f29a7.js","e27746418e02f75593a93b958a60807e"],["dev.sendSlackMessage.da8e22f4.js","ccb10829f18a727b79a5e8631bc4b2a2"],["index.html","02ff43eabc33c600e98785cffe7597d9"],["lib.gemini-scrollbar.df2fbf63.js","3941036bacb4a1432d22151ea7da073b"],["lib.isotope-horizontal.1604d974.js","6ac56d4296468c4428b5b5301a65938a"],["lib.isotope-packery.fabb73c3.js","808061641596e4b0ea2158b42d65915a"],["lib.react-color.265a6de0.js","f23f63d7e6934e381ffdc0405ecb449a"],["lib.react-date-picker.0a81fec3.js","778d1626645e439ad01e182ee589971a"],["lib.react-select.c768cf77.js","c8782991a161790ef2c9a2c7677da313"],["main.43e29bc6.js","fe6a6277acaa2a369ef235961be7bbcf"],["route.admin.CleanupPage.8b4bbf8e.js","8ab20412329e1ba4adc327713135be97"],["route.app.CmsPage.8a4303fb.js","0bf1506869db24bb9789c8a46449c8ad"],["route.app.CmsPageWrapper.accdebcc.js","c91e77aa8b518e1878761269deac22b6"],["route.app.ContactPage.75693d32.js","a530b00a5230a44897c2bf0aa9f402a8"],["route.app.PasswordResetExpiredDialog.65431bae.js","b5eef791dbd68edd4769bd6da022a857"],["route.app.SeriesDetailsPage.11a6989b.js","c52178b57767ae1bf94a9594cb32718e"],["route.cms.MetadataFormsPage.636188d2.js","e1e592b7e3dd82af774ac215524465c0"],["route.cms.PermissionsListPage.0e1e3075.js","9a3cc340a64238a1ab3ba1c0d483b7bd"],["route.cms.PermissionsPage.78a69f60.js","4b18e646715d6268e7aba3932f4e04a9"],["route.cms.SysconfigPage.f699b871.js","79bd1275213478f2ff1970e0b7341c49"],["styles.43e29bc620615f854714.css","b2e9e55e9ee2def2ae577ee1aaebda8f"],["styles.e0c12bb1c77e645e92d3.css","626778177949d72221e83b601c6c2c0f"],["translations.en.83fced0e.js","7e5509c23b5aafc1744a28a85c2950bb"],["translations.nl.4ae0b3bb.js","515ec7be7352a0c61e4aba99f0014a39"],["vendor.e0c12bb1.js","36ce9e0a59c7b4577b2ac4a637cb4238"]];
var cacheName = 'sw-precache-v3-nisv-client-' + (self.registration ? self.registration.scope : '');
Questions:
Why is the 'new or updated content' case happening so often?
Is the assumption correct that case 'installed': if (navigator.serviceWorker.controller) { means New or updated content is available?
How should I debug the problem?
A service worker has a life cycle:
- registered
- installing
- installed
- waiting
- activated
- redundant
When you register a service worker, it is fetched from the server, and it's lifespan in the disk cache depends on the cache-control response headers that your web server sends it with.
It is considered best practice, to have a cache-control : no-store must-revalidate along with a max-age : 0 if possible.
If you forget, the service worker is usually considered stale after 24 hours of registration, and is refetched and disk caches updated.
There are manual upgrades you can do, by bumping up a cache version on the service worker, or changing the service worker code. Even a byte change makes the browser view the service worker as a new one, and it installs the new service worker.
This however, does not mean, the old service-worker is discarded. It is imperative that any user close all the tabs / navigate away from your site, for the browser to discard the old service worker, and activate the new one.
you can bypass this behavior, by a self.skipWaiting in your install handler
That said,
While developing, you have the option of opening the browser's (chromium)
dev panel, navigate to the application tab, and check the box that says
update on reload
What this does is, immediately discard the old service worker irregardless of whether there is a change in your service worker code.
Regarding your questions:
Is the assumption correct that case 'installed': if (navigator.serviceWorker.controller) { means New or updated content is available?
NO. This means, that the new service worker, say sw-v1, is installed, and is currently waiting to activate.
Why is the 'new or updated content' case happening so often?
you say, you are using sw-precache. Since, I haven't used it myself, I have to add, a counter question
Is the plugin configured, to pick up changes to your static assets, and automatically trigger a code change or a cache bump in your service-worker ?
If so, then, any new version of your service worker, must only be waiting and not activating on a change.
The probable reason for this skipping of the waiting stage is,
you have a self.skipWaiting inside your install event inside your service worker.
Note the above is true, ONLY if
the change to your content happens. AND
that change triggers a service worker update
In this scenario, you might want to consider commenting out the self.skipWaiting statement. Or, maybe you could configure your plugin, to not skip on update - this matters on your content, and you need to take a call
In lieu of the fact that your assets changed and/ or triggered an update,
The other (most probable) reason would have to be,
you have an update on reload box checked under the application - > service workers tab on your dev console.
Uncheck that box, if checked, and then clear all caches, unregister all workers. After , open a new tab and continue testing the behavior.