Can you disable a service worker before the page loads? - javascript

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.

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

How do I prevent service worker from deleting cache, PWA

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

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

Service Worker is "installing" with every page reload though the sw.js did not change

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.

Retrieve html content of a page several seconds after it's loaded

I'm coding a script in nodejs to automatically retrieve data from an online directory.
Knowing that I had never done this, I chose javascript because it is a language I use every day.
I therefore from the few tips I could find on google use request with cheerios to easily access components of dom of the page.
I found and retrieved all the necessary information, the only missing step is to recover the link to the next page except that the one is generated 4 seconds after loading of page and link contains a hash so that this step Is unavoidable.
What I would like to do is to recover dom of page 4-5 seconds after its loading to be able to recover the link
I looked on the internet, and much advice to use PhantomJS for this manipulation, but I can not get it to work after many attempts with node.
This is my code :
#!/usr/bin/env node
require('babel-register');
import request from 'request'
import cheerio from 'cheerio'
import phantom from 'node-phantom'
phantom.create(function(err,ph) {
return ph.createPage(function(err,page) {
return page.open(url, function(err,status) {
console.log("opened site? ", status);
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function(err) {
//jQuery Loaded.
//Wait for a bit for AJAX content to load on the page. Here, we are waiting 5 seconds.
setTimeout(function() {
return page.evaluate(function() {
var tt = cheerio.load($this.html())
console.log(tt)
}, function(err,result) {
console.log(result);
ph.exit();
});
}, 5000);
});
});
});
});
but i get this error :
return ph.createPage(function (page) {
^
TypeError: ph.createPage is not a function
Is what I am about to do is the best way to do what I want to do? If not what is the simplest way? If so, where does my error come from?
If You dont have to use phantomjs You can use nightmare to do it.
It is pretty neat library to solve problems like yours, it uses electron as web browser and You can run it with or without showing window (You can also open developer tools like in Google Chrome)
It has only one flaw if You want to run it on server without graphical interface that You must install at least framebuffer.
Nightmare has method like wait(cssSelector) that will wait until some element appears on website.
Your code would be something like:
const Nightmare = require('nightmare');
const nightmare = Nightmare({
show: true, // will show browser window
openDevTools: true // will open dev tools in browser window
});
const url = 'http://hakier.pl';
const selector = '#someElementSelectorWitchWillAppearAfterSomeDelay';
nightmare
.goto(url)
.wait(selector)
.evaluate(selector => {
return {
nextPage: document.querySelector(selector).getAttribute('href')
};
}, selector)
.then(extracted => {
console.log(extracted.nextPage); //Your extracted data from evaluate
});
//this variable will be injected into evaluate callback
//it is required to inject required variables like this,
// because You have different - browser scope inside this
// callback and You will not has access to node.js variables not injected
Happy hacking!

Categories