Checking the ‘service worker’ documentation I understood that this is not a topic directly related to F7.
Anyway, I found a solution that can be helpful to others in the community.
export function forceServiceWorkerUpdate() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
// registration.update(); does not work!
registration.unregister();
}
window.location.reload(true);
});
}
}
Let’s take a look at the service worker.
Given this function (in the service worker):
function cacheRequestResponse(request,response){
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
//IMPORTANT: Ignore the "/watcher.js" script.
//This should be the only file in your application that does not get cached locally.
//The reason being is that this script should fulfill the function of an "updater" of sorts,
//which will notify the client when there's an update by uncaching specific files (implemented manually),
//and in order to do that this script must always be served directly by the server.
if(request.url.endsWith(".updated.js")) 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.
let responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(request, responseToCache);
});
}
This function will return files from the local cache unless their name ends with “.updated.js”.
If their name ends with “.updated.js” return a network response, aka the browser will ask the actual server for the contents of the file.
At this point all you have to do is make some kind of contract between your entry file and your .updated.js file.
For example I’m using svelte, so I make a global store that stores my local “lazy” version of the application, and at the same time my .updated.js file contains also a “live” version number.
Whenever I make a change in my source code, I make sure these 2 numbers are hard coded and are the same (in my source code).
Whenever I update my .updated.js file, my clients will fetch that file updated (cause, remember, my service worker always tries to fetch that one from the server) their .updated.js file will have a function that checks if the 2 versions match.
When I push an update, their svelte store version won’t match and that will trigger my .updated.js file to, in my case, remove all the local cache and redownload the files from the server using this piece of code:
recieves a callback that gets called and gets passed the actual live version and you can make your own store to save the “lazy” version number so to speak.
// When a new service worker is installed, we notify the user so he can restart the app
f7.on('serviceWorkerRegisterSuccess', (registration) => {
console.log('serviceWorkerRegisterSuccess');
const reg = registration;
reg.onupdatefound = () => {
console.log('serviceWorker onupdatefound');
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
console.log('serviceWorker installingWorker onstatechange');
if (installingWorker.state === 'installed') {
console.log('serviceWorker installed');
if (navigator.serviceWorker.controller) {
console.log('serviceWorker navigator.serviceWorker.controller');
// We inform the user a new update is available
this.notifyUserOfUpdate(() => {
window.location.reload();
});
}
}
};
};
});
When it detects a new version, it displays a notification. If the user click the notification, the page automatically reloads. Otherwise, the new service worker will be installed the next time the user load the app.
For people like me who use F7 core and need a mandatory restart:
Into workbox-config.js add:
skipWaiting: true
Into app.js add:
app.on('serviceWorkerRegisterSuccess', (registration) => {
const reg = registration;
//add event in routeChange because page doesn't change so i manually update service
app.on('routeChange', () => {
reg.update();
});
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
app.dialog.create({
text: 'To be sure of the functioning of the application, press the update button.',
title: `Update ${app.name} detected!`,
buttons: [
{
text: 'Update',
onClick: (dialog, e) => {
window.location.reload();
}
},
{
text: 'Close',
onClick: (dialog, e) => {
dialog.close();
}
}
],
verticalButtons: true
}).open();
}
};
};
});
Another piece of advice that I give you to prevent the dialogue from activating (on firefox/edge) even on the first start, just put an init event on the application instance with a “first start” variable like:
var app = new Framework7({
//other code
on: {
init: function (page) {
var f7 = this;
f7.firstStart = true;
}
}
)};
And change this line into reg.onupdatefound event:
if (installingWorker.state === 'installed' && !app.firstTime) {
// other code.
} else {
app.firstTime = false;
}