How to update cache in PWA (No Framework) - javascript

Am working on PWA and am trying to make my App works offline, so i cache my assets and its works fine, i could even use my app through my cached assets, BUT the problem is whenever i delete my cache using dev tools or whenever i update something in my assets my app not regenerate the cache again, how to regenrate the cache everytime when i start using the app online
Service worker registeration (app.js):
if('serviceWorker' in navigator){
navigator.serviceWorker.register('./sw.js')
.then((reg) => console.log('Service worker registered', reg))
.catch((err) => console.log('Service worker not registered', err));
}
My service worker looks like this:
const cacheName = 'VSCode_SG_' + Date.now(),
assets = [
'./',
'./app.min.js',
'./favicon.ico',
'./style.css',
];
self.addEventListener('install', evt => {
evt.waitUntil(caches.open(cacheName).then(cache => {
cache.addAll(assets);
}))
});
self.addEventListener('activate', evt => {
// Get all the currently active `Cache` instances.
evt.waitUntil(caches.keys().then((keys) => {
// Delete all caches that aren't in the allow list:
return Promise.all(keys.map((key) => {
if (!cacheName.includes(key)) {
return caches.delete(key);
}
}));
}));
});
self.addEventListener('fetch', evt => {
evt.respondWith(
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request);
}).catch(() => console.log('An error has occurred'))
);
});
am sure that am missing a small detail to solve the problem but i don't have any clues, anyone could help?

I think your issue might be related to your caches name, you want it to stay a static name instead of labeling it with a date stamp
const cacheName = 'VSCode_SG_STATIC', // keep it a constant string
assets = [
'./',
'./app.min.js',
'./favicon.ico',
'./style.css',
];
Give this a shot and wait until your new service worker is installed to try again

Related

Prompting only once when there are multiple workbox-broadcast-update messages

I'm using Workbox and the BroadcastUpdatePlugin() in my serviceWorker to prompt the user to refresh the page when a cached file is updated. It works great when there's only one file updated, but when I publish multiple updates at once (HTML, CSS and JS files), the user is prompted to refresh the page for each file.
How can I update all files in the cache, then prompt the user to refresh the page only once, when the event listener has stopped receiving update messages?
ServiceWorker code
const {BroadcastUpdatePlugin} = workbox.broadcastUpdate;
registerRoute(
({request}) => request.destination === 'document',
new StaleWhileRevalidate({
//new NetworkOnly({
cacheName: 'pages',
plugins: [
new BroadcastUpdatePlugin(),
],
})
)
registerRoute(
({request}) => request.destination === 'script' || request.destination === 'style',
new StaleWhileRevalidate({
//new NetworkOnly({
cacheName: 'assets',
plugins: [
new BroadcastUpdatePlugin(),
],
})
)
JavaScript code
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js', { scope: '/' }).then(swReg => {
console.log('Service Worker Registered', swReg);
}).catch(error => {
console.log('There was an error!', error);
})
})
// Listen for cache updates and prompt a page reload
navigator.serviceWorker.addEventListener('message', async (event) => {
if (event.data.meta === 'workbox-broadcast-update') {
const {cacheName, updatedURL} = event.data.payload;
const cache = await caches.open(cacheName);
const updatedResponse = await cache.match(updatedURL);
const updatedText = await updatedResponse.text();
console.log('Updated: '+cacheName+', '+updatedURL);
// prompts for every update
if(confirm('Content Updated. Please refresh the page.')){
window.location.reload;
}
}
})
}
I could imagine there potentially being logic in your service worker that could help with this, by, for instance, looking at the cliendId in the FetchEvent that triggered an update and only conditionally sending the message if it hasn't seen that clientId before.
But that would require a bunch of custom logic beyond what BroadcastUpdatePlugin provides already, and it might be error proneā€”if your service worker broadcasts an update, then the user acts on the update, and then there's another update that could be broadcast, you probably want to give the user another chance to act on it, but it would be difficult for the service worker to know whether to broadcast a message to the same clientId in that scenario.
A cleaner approach would be to move the logic that prevented multiple prompt to the window client code.
There are a couple of ways to go there, but what seems the cleanest to me would rely on the resolution of a promise to trigger the prompt. You can call the resolve function of a promise multiple times, but the then() in the promise chain will only be invoked once, giving you the behavior you're looking for.
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
const waitForUpdate = new Promise((resolve) => {
navigator.serviceWorker.addEventListener('message', async (event) => {
if (event.data.meta === 'workbox-broadcast-update') {
const {cacheName, updatedURL} = event.data.payload;
const cache = await caches.open(cacheName);
const updatedResponse = await cache.match(updatedURL);
const updatedText = await updatedResponse.text();
console.log(`Updated ${updatedURL} in ${cacheName}: ${updatedText}`);
// Calling resolve() will trigger the promise's then() once.
resolve();
}
});
});
waitForUpdate.then(() => {
if (confirm('Content updated. Please refresh the page.')) {
window.location.reload();
}
});
}
(You could also use an approach that relied on a global variable or something similar that gets flipped the first time you show the prompt, and short-circuits the prompt display every subsequent time, but I like using promises for this sort of thing.)

Files are not getting cached in ServiceWorker

I have migrated my existing website to PWA.
My website is very simple made using HTML, JQuery and javascript.
Previously the caching mechanism was dependent on manifest.appcache.
Now when I try to cache the files using service worker none of the file are getting cached.
I have checked my service worker is registered and everything seems fine but files are not getting stored in cache.
I tried clearing the cache but no help.
I am sharing my service worker code below since it is production code I can not share all file names.
I used this site to learn PWA.
const staticCacheName = 'site-static-v4';
const dynamicCacheName = 'site-dynamic-v4';
const assets = [
"index.shtml",
"fallback.html",
"manifest.webmanifest"
];
// cache size limit function
const limitCacheSize = (name, size) => {
caches.open(name).then(cache => {
cache.keys().then(keys => {
if(keys.length > size){
cache.delete(keys[0]).then(limitCacheSize(name, size));
}
});
});
};
// install event
self.addEventListener('install', evt => {
//console.log('service worker installed');
evt.waitUntil(
caches.open(staticCacheName).then((cache) => {
console.log('caching shell assets');
cache.addAll(assets);
})
);
});
// activate event
self.addEventListener('activate', evt => {
//console.log('service worker activated');
evt.waitUntil(
caches.keys().then(keys => {
//console.log(keys);
return Promise.all(keys
.filter(key => key !== staticCacheName && key !== dynamicCacheName)
.map(key => caches.delete(key))
);
})
);
});
// fetch event
self.addEventListener('fetch', evt => {
//console.log('fetch event', evt);
evt.respondWith(
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request).then(fetchRes => {
return caches.open(dynamicCacheName).then(cache => {
cache.put(evt.request.url, fetchRes.clone());
// check cached items size
limitCacheSize(dynamicCacheName, 15);
return fetchRes;
})
});
}).catch(() => {
if(evt.request.url.indexOf('.html') > -1){
return caches.match('fallback.html');
}
})
);
});
Below is the screenshot of my empty cache storage.

React App with Service worker - Force update cache

I am trying different approaches to clearing the browser cache on application update.
This has proven to be more difficult than expected. :)
My refresh on new version code looks like this.
My app mounts in app.js
I have this code in serviceWorker-base.js (I am using workbox)
addEventListener('message', function(messageEvent){
if (messageEvent.data === 'skipWaiting') return skipWaiting();
});
My app.js
// skipWaiting() functions
function promptUserToRefresh(registration) {
// this is just an example - don't use window.confirm in real life; it's terrible
if (window.confirm("New version available! Refresh?")) {
registration.waiting.postMessage('skipWaiting')
}
}
function listenForWaitingServiceWorker(registration) {
console.log('listenForWaitingServiceWorker', registration)
function awaitStateChange() {
registration.installing.addEventListener('statechange', function() {
if (this.state === 'installed') promptUserToRefresh(registration)
})
}
if (!registration) return
if (registration.waiting) return promptUserToRefresh(registration)
if (registration.installing) awaitStateChange()
registration.addEventListener('updatefound', awaitStateChange)
}
//**
const enableServiceWorker = true
const serviceWorkerAvailable = ('serviceWorker' in navigator) ? true : false
// Register service worker
if (enableServiceWorker && serviceWorkerAvailable) {
navigator.serviceWorker.register('/serviceWorker.js')
.then( (registration) => {
console.log('Service worker registered', registration)
listenForWaitingServiceWorker(registration) // ** skipWaiting() code
})
}
// Install prompt event handler
export let deferredPrompt
window.addEventListener('beforeinstallprompt', (event) => {
// Prevent Chrome 76 and later from showing the mini-infobar
event.preventDefault()
deferredPrompt = event // Stash the event so it can be triggered later.
try{
showInstallPromotion()
}catch(e){
console.error('showInstallPromotion()', e)
}
})
window.addEventListener('appinstalled', (event) => {
console.log('a2hs installed')
})
My question is - Would it be possible to add a function to clear the browser cache when the user clicks "OK"?
Something along the lines of:
const refreshCacheAndReload = () => {
if (caches) {
// Service worker cache should be cleared with caches.delete()
caches.keys().then((names) => {
for (const name of names) {
caches.delete(name);
}
});
}
// delete browser cache and hard reload
window.location.reload(true);
};
Or are there better ways to force the browser to clear the cache?
Kind regards/K
UPDATE
The reason for wanting to clear the cache is to force the client to receive a new version of the application when a new release is deployed. (bundle files, css-files, etc.) As of today this requires hard reload and/or several reloads. Sometimes the application only receives parts of the new application on first reload.

How can I force service worker to clear cache? [duplicate]

So, I have an HTML page with service worker,
the service worker cache the index.html and my JS files.
The problem is when I change the JS, the change doesn't show up directly on the client browser. Of course in chrome dev-tools, I can disable cache. But in chrome mobile, how do I do that?
I tried to access the site settings and hit the CLEAR % RESET button.
But it still loads the old page/load from cache.
I tried to use other browser or chrome incognito and it loads the new page.
Then, I try to clear my browsing data (just cache) and it works.
I guess that's not how it should work right? my user won't know if the page is updated without clearing the chrome browser cache.
If you know the cache name you can simply call caches.delete() from anywhere you like in the worker:
caches.delete(/*name*/);
And if you wanted to wipe all caches (and not wait for them, say this is a background task) you only need to add this:
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
Use this to delete outdated caches:
self.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);
})
);
})
);
});
Typically you update the CACHE_NAME in your service workers JS file so your worker installs again:
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(inputs))
)
})
Alternatively, to clear the cache for a PWA find the cache name:
self.caches.keys().then(keys => { keys.forEach(key => console.log(key)) })
then run the following to delete it:
self.caches.delete('my-site-cache')
Then refresh the page.
If you see any worker-related errors in the console after refreshing, you may also need to unregister the registered workers:
navigator.serviceWorker.getRegistrations()
.then(registrations => {
registrations.forEach(registration => {
registration.unregister()
})
})
The most elegant solution, with async/await:
const cacheName = 'v2';
self.addEventListener('activate', event => {
// Remove old caches
event.waitUntil(
(async () => {
const keys = await caches.keys();
return keys.map(async (cache) => {
if(cache !== cacheName) {
console.log('Service Worker: Removing old cache: '+cache);
return await caches.delete(cache);
}
})
})()
)
})
This is the only code that worked for me.
It is my adaptation of Mozilla documentation :
//Delete all caches and keep only one
const cachNameToKeep = 'myCache';
//Deletion should only occur at the activate event
self.addEventListener('activate', event => {
var cacheKeeplist = [cacheName];
event.waitUntil(
caches.keys().then( keyList => {
return Promise.all(keyList.map( key => {
if (cacheKeeplist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
.then(self.clients.claim())); //this line is important in some contexts
});

How to clear cache of service worker?

So, I have an HTML page with service worker,
the service worker cache the index.html and my JS files.
The problem is when I change the JS, the change doesn't show up directly on the client browser. Of course in chrome dev-tools, I can disable cache. But in chrome mobile, how do I do that?
I tried to access the site settings and hit the CLEAR % RESET button.
But it still loads the old page/load from cache.
I tried to use other browser or chrome incognito and it loads the new page.
Then, I try to clear my browsing data (just cache) and it works.
I guess that's not how it should work right? my user won't know if the page is updated without clearing the chrome browser cache.
If you know the cache name you can simply call caches.delete() from anywhere you like in the worker:
caches.delete(/*name*/);
And if you wanted to wipe all caches (and not wait for them, say this is a background task) you only need to add this:
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
Use this to delete outdated caches:
self.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);
})
);
})
);
});
Typically you update the CACHE_NAME in your service workers JS file so your worker installs again:
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(inputs))
)
})
Alternatively, to clear the cache for a PWA find the cache name:
self.caches.keys().then(keys => { keys.forEach(key => console.log(key)) })
then run the following to delete it:
self.caches.delete('my-site-cache')
Then refresh the page.
If you see any worker-related errors in the console after refreshing, you may also need to unregister the registered workers:
navigator.serviceWorker.getRegistrations()
.then(registrations => {
registrations.forEach(registration => {
registration.unregister()
})
})
The most elegant solution, with async/await:
const cacheName = 'v2';
self.addEventListener('activate', event => {
// Remove old caches
event.waitUntil(
(async () => {
const keys = await caches.keys();
return keys.map(async (cache) => {
if(cache !== cacheName) {
console.log('Service Worker: Removing old cache: '+cache);
return await caches.delete(cache);
}
})
})()
)
})
This is the only code that worked for me.
It is my adaptation of Mozilla documentation :
//Delete all caches and keep only one
const cachNameToKeep = 'myCache';
//Deletion should only occur at the activate event
self.addEventListener('activate', event => {
var cacheKeeplist = [cacheName];
event.waitUntil(
caches.keys().then( keyList => {
return Promise.all(keyList.map( key => {
if (cacheKeeplist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
.then(self.clients.claim())); //this line is important in some contexts
});

Categories

Resources