-
-
Save kawazoe/fa3b5a3c998d16871ffb9e2fd721cb4b to your computer and use it in GitHub Desktop.
// ------------------------------------------- IMPORTANT ------------------------------------------- | |
// This is a development file to be minified using https://javascript-minifier.com/ and inlined in | |
// the index.html file. This file is not compiled or processed by webpack so it should be treated as | |
// low-level precompiled es5-compatible javascript. The code here is not meant to be clean, it's | |
// meant to be as light and fast as possible since it runs in the head tag. | |
// HACK: This file a hack to ensure that home-screen apps on mobile devices gets refreshed when they | |
// start. It works by forcing a load of the service-worker.js file and use the precache-manifest | |
// file name as an application version, just like a desktop browser like chrome would do. When | |
// when it detects a change in the application version, it reloads the page and bypass the browser's | |
// cache. This should force mobile devices to reload the new version of the app even if they cached | |
// an older version of the site. | |
(function () { | |
var r = new XMLHttpRequest(); | |
r.onload = function () { | |
var t = r.responseText; | |
var versionStart = t.indexOf('"/precache-manifest.') + 20; | |
var versionEnd = t.indexOf('.js"', versionStart); | |
if (versionEnd - versionStart === 32) { | |
var ls = localStorage; | |
var oldPrecacheManifestVersion = ls.getItem('pmv'); | |
var newPrecacheManifestVersion = t.substring(versionStart, versionEnd); | |
if (newPrecacheManifestVersion !== oldPrecacheManifestVersion) { | |
ls.setItem('pmv', newPrecacheManifestVersion); | |
return window.location.reload(true); | |
} | |
} | |
}; | |
r.open('GET', '/service-worker.js?c=' + new Date().getTime()); | |
r.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); | |
r.send(); | |
}()); |
<script type="application/javascript">!function(){var e=new XMLHttpRequest;e.onload=function(){var n=e.responseText,t=n.indexOf('"/precache-manifest.')+20,o=n.indexOf('.js"',t);if(o-t==32){var r=localStorage,i=r.getItem("pmv"),s=n.substring(t,o);if(s!==i)return r.setItem("pmv",s),window.location.reload(!0)}},e.open("GET","/service-worker.js?c=" + new Date().getTime()),e.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate'),e.send()}();</script> |
So I also figured out that the latest version of Safari seems to also cache the service-worker.js
file if you configure the service worker to cache it (even on accident). This seems to be the source of really all the problems I've been having (although I left the focus listener in just in case).
I had this within my web pack configuration:
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
cleanupOutdatedCaches: true,
offlineGoogleAnalytics: true,
runtimeCaching: [
{
urlPattern: /.*/,
handler: 'networkFirst'
}
]
})
What I noticed is that a force refresh in the browser (on MacOS) would load the new service-worker.js
but the subsequent refresh (without a force) would actually bring up the older one. So I noticed the browser would swap between the versions seemingly randomly. I changed my runtime caching to be much more specific and things are stable now.
Edit:
Also now got rid of the forced update via the focus listener as the normal updating (via timed update()
checks works just fine/consistently when the service-worker.js
file isn't cached by itself. Also the problem with this forced reload technique is that the reload itself can happen before the service worker has truly updated (causing multiple refreshes to get the latest app).
In my index.html I have this (which calls the service worker 'update' every 2 minutes after an initial 30s check):
window.isUpdateAvailable = new Promise(function (resolve) {
// lazy way of disabling service workers while developing, since no https
if ('serviceWorker' in navigator && location.protocol.startsWith('https')) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js')
.then(reg => {
resolve(reg);
setTimeout(function update() {
reg.update().catch((err) => {
console.log('[SW UPDATE ERROR]:' + err);
});
setTimeout(update, 120000);
}, 30000);
})
.catch(err => console.error('[SW ERROR]', err));
});
}
});
Then in my code (Angular + Material) I do this to 'show a prompt' to the user to reload:
ngOnInit(): void {
window['isUpdateAvailable']
.then((reg) => {
if (reg) {
this.setupListener(reg);
}
});
}
private setupListener(reg) {
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
this._ngZone.run(() => {
this.openSnackBar();
});
}
};
};
}
private openSnackBar() {
if (!this.dialogOpen) {
this.dialogOpen = true;
this.snackBar.openFromComponent(NewApplicationVersionDataComponent, {
duration: -1,
verticalPosition: 'bottom',
horizontalPosition: 'center'
}).afterDismissed().subscribe(() => {
this.dialogOpen = false;
});
}
}
Let's assume you already have the app installed without that code implemented: there is no way to force update existing PWAs right away right? I guess you just need to wait until the iPhone refreshes the cache automatically, replace the old index file by the new one including the script and only by then is updating as desired based on that script.
@alexeigs That is right. As far as I am aware off, the only way to reliably force an update in this case is to clear safari's cache. I have seen this cache survive OS updates so I wouldn't count on this happening by itself too much.
Ok still at it... definitely not consistent (getting old versions of my index page even with
reload(true)
. I'm trying this script out to see how it does (in addition to my above post). It seems to be working with one extra reload to set the initial version. I also changed the 'find the version string' which wasn't quite right for my version of webpack as the url didn't start with a slash /.