How do I prevent service worker from deleting cache, PWA - javascript

I have built PWA enabled SPA by using Vue, js, node technology. Problem is that service worker deletes my stored cache for viewing app offline whenever i switch off my PC, otherwise it works. Second problem is that it refuses to fetch data that includes google fonts, unless I enable CORS in the browser. Since PWA is based on the browser and users will not have CORS add-on installed in the browser, is there some way to enable CORS on the (Windows) server? Thanks in advance, here is my service worker code.
// service worker file. Every time when you change this file rename staticCache const in order to changes to be visible when user closes tab and reopens it.
const staticCache = 'site-static-1'; // static cache for main site files, app shell (all static files, html, css, starting images, logo etc). If you change code always rename this to new number
const assets = [ // this is array of app shell API requests for assets. Those are keys and asset values (images etc) will be values of key/value pair in array
'/',
'/index.html',
'/app.c328ef1a.js',
'/app.c328ef1a.css',
'/manifest.webmanifest',
'/photo-login.04703ebf.jpg',
'/icon_area.9bfa0c9a.png',
'/icon_144x144.c75152b5.png',
'/img/avatar_default.png',
'/img/icon_pwa.png',
'https://fonts.googleapis.com/css?family=Raleway:300,400,600,700',
'https://code.highcharts.com/stock/highstock.js',
'https://code.highcharts.com/stock/modules/data.js',
'https://code.highcharts.com/stock/modules/exporting.js',
'https://cdn.pubnub.com/sdk/javascript/pubnub.4.21.7.min.js',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwIYqWqhPAMif.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptug8zYS_SKggPNyCMIT5lu.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptug8zYS_SKggPNyC0ITw.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwPIsWqhPAMif.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwPIsWqZPAA.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwJYtWqhPAMif.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwJYtWqZPAA.woff2'
]; // we hve to fetch separately fonts from links inside fonts.googleapis.com
// In Chrome Web Tools go to Application>Cache storage and click /css?family=raleway, links are inside value of that key
// installing service worker event
self.addEventListener('install', evt => {
console.log('Service worker has been installed');
// programmatically skip awaiting for new changed sw file to become active, because sometimes closing Chrome and tabs is not enough
// if we change sw.js and want to make sure change is visible ie cache is refreshed, we need to change version number of staticCache constant.
// NOTE: If we save this file by adding asset to be fetched (image for example) it will be visible in a new cache upon clicking browser reload.
// ..but if we delete it from the list of items to be fetched, IT WILL REMAIN in the cache until we change staticCache version number, save and reload browser page.
// So it is best practice to always change version number in staticCache whenever you make and save changes.
self.skipWaiting(); // it will be triggered only if there is a new sw version that awaits to be executed
evt.waitUntil( // installing awaits until this is executed first, otherwise it could stop it
caches.open(staticCache).then(cache => { // it opent cache, if there isn't it will create one
cache.addAll(assets); // add into cache all assets from the assets array []
})
);
});
// activating service worker event
self.addEventListener('activate', evt => {
console.log('Service worker has been activated');
evt.waitUntil(
caches.keys().then(keys => { // get array with keys (of key/value pair) from different cache versions in Chrome Dev Tools>Application>Cache Storage
// go thru all caches keys array and delete all values except newest cache, named in staticCache const. That way only the last cache is used by an app
return Promise.all(keys
.filter(key => key !== staticCache)
.map(key => caches.delete(key))
)
})
);
});
// fetch event
self.addEventListener('fetch', evt => {
console.log('SW is fetching data');
evt.respondWith( // check if requested data is stored in the cache.
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request) // if item is in cache use it, if isn't go to the server and fetch it
})
)
});

Seems like the issue is with the activate event handler in the service worker. This is mainly using for removing the old cache from the browser. Try replacing the event listener with the following code
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== staticCache) {
return caches.delete(cache); //Deleting the old cache (cache v1)
}
})
);
})
.then(function () {
console.info("Old caches are cleared!");
return self.clients.claim();
})
);
});

Related

Service Worker problems: cannot get web app to use newly installed cache version of JS file

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...');
}
});

Workbox - Background Sync - Offline Post - Replay Events when the browser is back online doesn't get triggered

I am using workbox v4.3.1 to provide offline capability to the users of the web application.
While everything works perfectly in Chrome as you would expect a PWA to work (i.e everything is cached locally, all the updates from the APP are captured in IndexedDB and synced back to the server when the application is back online.
However the major use case for me is to provide support for iOS Safari and as a PWA.
While all the pages are cached locally using the Service Worker in Safari and all the offline updates are also captured in the indexed DB as shown below,
However, when the connection returns online, the sync event is not triggered by the browser (Safari in this case). While background sync is not supported natively by Safari, I would expect that when I refresh the page, SW initialisation should trigger the sync event manually if it finds some data to be refreshed to the server in the indexed DB.
But this is not happening and I tried to manually listen for the "message" - "replayRequests" and then replay the requests - that did not work as well.
Any help here would be appreciated. Here is the service worker code for reference.
// If we're not in the context of a Web Worker, then don't do anything
if ("function" === typeof importScripts) {
importScripts(
"https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"
);
//Plugins
// Background Sync Plugin.
const bgSyncPlugin = new workbox.backgroundSync.Plugin("offlineSyncQueue", {
maxRetentionTime: 24 * 60
});
// Alternate method for creating a queue and managing the events ourselves.
const queue = new workbox.backgroundSync.Queue("offlineSyncQueue");
workbox.routing.registerRoute(
matchCb,
workbox.strategies.networkOnly({
plugins: [
{
fetchDidFail: async ({ request }) => {
await queue.addRequest(request);
}
}
]
}),
"POST"
);
// CacheKeyControlPlugin
const myCacheKeyPlugin = {
cacheKeyWillBeUsed: async ({ request, mode }) => {
normalizedUrl = removeTimeParam(request.url);
return new Request(normalizedUrl);
}
};
if (workbox) {
console.info("SW - Workbox is available and successfully installed");
} else {
console.info("SW - Workbox unavailable");
}
//Intercept all api requests
var matchCb = ({ url, event }) => {
// Filter out the presence api calls
return url.pathname.indexOf("somethingidontwanttocache") == -1;
};
function removeTimeParam(urlString) {
let url = new URL(urlString);
url.searchParams.delete("time");
return url.toString();
}
/* //Pre cache a page and see if it works offline - Temp code
workbox.precaching.precache(getPageAPIRequestURLs(), {
cleanUrls: false
}); */
workbox.routing.registerRoute(
matchCb,
new workbox.strategies.CacheFirst({
cacheName: "application-cache",
plugins: [myCacheKeyPlugin]
})
);
self.addEventListener("message", event => {
if (event.data === "replayRequests") {
queue.replayRequests();
}
});
}
workbox-background-sync emulates background sync functionality in browsers that lack native support by replaying queued requests whenever the service worker process starts up. The service worker process is meant to be lightweight and short lived, and is killed aggressively when there's a period of time without any events, and then is started up again in response to further events.
Reloading a web page may cause the service worker process to start up, assuming it had previously been stopped. But if the service worker is still running, then reloading the page will just cause a fetch event to be fired on the existing process.
The interval at which a service worker process can remain idle before it's killed is browser-dependent.
Chrome's DevTools offers a method of inspecting the state of a service worker and starting/stopping it on demand, but I don't believe Safari's DevTools offers that functionality. If you wanted to guanratee that a service worker was stopped and then start it up again, I would quit Safari, reopen it, and then navigate back to your web app.

How to update Reactjs based PWA to the new version?

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();
});
},
});

Can you disable a service worker before the page loads?

My team and I have a project that was originally built as a PWA, but have since decided to scrap that idea as we realized it would need to change much more frequently than originally intended. However, the service worker is already live, as well as a newly redesigned landing page for the website. Despite all our efforts to clear the PWA caching, our clients are still reporting that they are receiving the old cached version of the website.
Currently, we have the service worker set up to delete all caches upon install (and whenever anything at all happens as a precaution), as well as some JavaScript to unregister the service worker when the new page actually loads. However, the problem is that none of this runs until the user makes a request to the website, and at that point the browser is already loading the cached content. Is it possible to clear this cache and prevent the browser from loading any content that was already cached?
Current service-worker.js
// Caching
var cacheCore = 'mkeSculptCore-0330121058';
var cacheAssets = 'mkeSculptAssets-0330121058';
self.addEventListener('install', function (event) {
self.skipWaiting();
caches.keys().then(function (names) {
for (let name of names)
caches.delete(name);
});
});
self.addEventListener('activate', function (event) {
caches.keys().then(function (names) {
for (let name of names)
caches.delete(name);
});
});
self.addEventListener('fetch', function (event) {
caches.keys().then(function (names) {
for (let name of names)
caches.delete(name);
});
});
Script in index.html
(function () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
//returns installed service workers
if (registrations.length) {
for (let registration of registrations) {
registration.unregister();
}
}
});
}
})();
So far, I've read a few other similar StackOverflow answers, including this one, but they tend to rely on users manually doing something to fetch the new content, ie. via a hard reload or disabling the service worker manually through the browser settings. However, in my case, we cannot rely on manual user actions.
One way to solve this issue is to add a timestamp at end of the file(js, css) name so each time when it is making a request, the cache key is not available in the service worker and thus it tends to get a new version of the file at each load.
<script type="text/javascript" src="/js/scipt1.js?t=05042018121212"/>
For appending a new timestamp dynamically in the file name, please check this answer
But this may not be reliable if HTML itself is cached.
add this before to "update" all contents:
$.each(['index.html','file1.js','file2.js','file3.js'],function(index,file) {
$.get(file+'?t='+new Date().getTime(), function(){});
});
location.reload(true);
for the ServiceWorker to stop all windows using it must be closed.
If it's a webapp you can use window.close();
This code just loads a fresh version of the files in the list.
If there are any internal caches they will be all updated.

What exactly triggers this service worker code to run?

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.

Categories