[Solved] PWA force refresh when new version released?
-
I have a solution finally!
forceSWupdate () { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistrations().then(function (registrations) { for (let registration of registrations) { registration.update() } }) } }
This solves both of my problems above, and I can put it into a method that is either user triggered or programatically. Hope this helps someone else.
-
I’ll add my observations, because I think this will be a recurring issue for lots of people.
From what I’ve seen, Quasar will automatically check for the existence of a new service worker when it loads. However by default, a PWA won’t start using the new service worker until it is completely shut down and restarted. Simply reloading, or even hard-refreshing the page won’t work (and PWAs in standalone mode don’t always have a refresh button available). This is particularly problematic on iOS at the moment - when you close a PWA, iOS “freezes” the state of the app rather than shutting it down. And there’s no way to easily shut down the app. I’ve found going into the settings for Safari and doing a full cache clear will let a new service worker take over the PWA, and the updated version will appear.
Enabling the skipWaiting() setting (which can be done in the quasar.conf.js file) will the PWA to start using the new service worker as soon as possible. The observed behaviour flow: app loads up, PWA checks for new service worker, identifies a new one is available and installs it. If the page is reloaded or revisited, the new service worker will take over and the updated version will display. The updated version isn’t shown absolutely immediately, but it is shown pretty fast.
One potential issue I’ve seen (not 100% confirmed). Using skipWaiting() in conjunction with lazy loading is a known potential risk: a new service worker might try to load content that is in control of the older service worker. We’ve seen new content load up, navigate to a different page, navigate back and the old version is displaying.
Edit: iOS 13 (coming out in a couple of weeks) brings back the ability to force quit a PWA. That might also influence the decision of whether to use skipWaiting().
-
@ssuess Hello, will this work on normal vue.js app? without using quasar? Thank you in advance. I’m also dealing on how to refresh the page for cache refreshing whenever it detects new changes.
-
@applecider Yes these techniques really don’t have much to do with quasar itself (although there are a few quasar specific settings related to injecting options into workbox, you can just add those yourself).
-
Further update now that that iOS 13 has been released.
I removed skipWaiting() and clientClaim() from our configuration settings. I can open our PWA and it will show the old version. After closing the app via the app switcher, when the app restarts, it has updated to the new version. iOS might close a suspended PWA after a certain amount of time has passed without any user action, although as with most things PWA related on iOS, information from Apple is scarce.
-
Sorry to resurrect this old thread but I am having the same problems as some users above. I have a PWA with a custom service worker. In App.vue in a preFetch method, I read a file called version.json and compare that with a localstorage “version” property. If the localstorage is lower, I then create a Quasar Dialog that there is a newer version available and then do window.location.reload(true) when the user clicks “ok”. That all works, however it doesn’t fetch the latest app data. Only when I either close the app and reopen it or in a browser I do a ctrl-f5 to refresh.
When changing to generatesw instead of injectmanifest, thus eliminating my custom service worker (which I want because of various route caching strategies), quasar creates a red bar that a new version is available and when clicking that, it fetches the latest version of the app from the server.
Does anyone know how generateSW is doing this and how I could duplicate that functionality in my custom service worker?
-
Thread with similar problem:
https://forum.quasar-framework.org/topic/6959/chrome-but-not-ff-nor-safari-randomly-fails-pwa-update/29?_=1604419034026btw I would start new thread for this.
-
@PiotrG I am pretty sure you are running into the same problem I was running into originally, which is that a simple reload will not work because you can’t let go of the app itself while you are using it without invoking the update method of your
registration
innavigator.serviceWorker
see above for that code, and also remember that you must haveskipWaiting
andclientsClaim
present in your service worker (they can be loaded into the service worker when using GenerateSW if you addworkboxOptions: { skipWaiting: true, clientsClaim: true }
in yourquasar.conf.js
file btw) -
@ssuess Which one are you using right now ? The last one posted here …
https://forum.quasar-framework.org/topic/2560/solved-pwa-force-refresh-when-new-version-released/31?_=1605731186657forceSWupdate () { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistrations().then(function (registrations) { for (let registration of registrations) { registration.update() } }) } }
-
@turigeza I use the function above when I want to trigger an update from within my code, and within my service worker I have code that checks my local version number, clears localstorage, and then forces a reload. I also have to unfortunately check in SW for what browser we are using, because different browsers behave differently when reloaded. You can see that code and explanation in this thread: https://forum.quasar-framework.org/topic/6959/solved-chrome-but-not-ff-nor-safari-randomly-fails-pwa-update/41?_=1605780104096
-
@ssuess said in [[Solved] PWA force refresh when new version released?](/post
@turigeza I use the function above when I want to trigger an update from within my code, and within my service worker I have code that checks my local version number, clears localstorage, and then forces a reload.
Mind sharing the snippet
-
I linked to it above, but here it is again in more or less final form (with domains changed):
import localforage from 'localforage' register(process.env.SERVICE_WORKER_FILE, { ready () { console.log('App is being served from cache by a service worker.') }, registered (registration) { // registration -> a ServiceWorkerRegistration instance console.log('Service worker has been registered.') // console.log('scope: ' + registration.scope) }, cached (registration) { // registration -> a ServiceWorkerRegistration instance console.log('Content has been cached for offline use.') }, updatefound (registration) { console.log('New content is available; please refresh.') /* eslint-disable no-extra-boolean-cast */ if (!!window.chrome) { // for chromium based browsers fetch('https://pwatest.mydomain.com/av.json?t=' + Date.now()) .then(response => { response.json().then(function (data) { const r = confirm('There is a new version (' + data.version + ') available. Your version will be updated, alright? ' + data.message) if (r === true) { if (data.clearStorage === true) { localforage.clear().then(function () { // Run this code once the database has been entirely deleted. console.log('Database is now empty. so there now.') location.reload(true) }).catch(function (err) { // This code runs if there were any errors console.log(err) }) } } else { console.log('You pressed Cancel!') } }) console.log('response:', response) }) .catch(error => { console.error(error) }) } }, updated (registration) { // registration -> a ServiceWorkerRegistration instance console.log('New content is available; please refresh.') if (!window.chrome) { // for non chromium browsers fetch('https://pwatest.mydomain.com/av.json?t=' + Date.now()) .then(response => { response.json().then(function (data) { const r = confirm('There is a new version (' + data.version + ') available. Your version will be updated, alright? ' + data.message) if (r === true) { if (data.clearStorage === true) { localforage.clear().then(function () { // Run this code once the database has been entirely deleted. console.log('Database is now empty. so there now.') location.reload(true) }).catch(function (err) { // This code runs if there were any errors console.log(err) }) } } else { console.log('You pressed Cancel!') } }) console.log('response:', response) }) .catch(error => { console.error(error) }) } }, offline () { console.log('No internet connection found. App is running in offline mode.') }, error (err) { console.error('Error during service worker registration:', err) } })
-
@ssuess Thank you for sharing the code and explaining it as well.
-
It seems that Quasar now supports this out of the box: https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa#Reload-%26-Update-Automatically
-
@danbars Quasar supports loading clientsClaim and skipWaiting directives into the SW file, but for a number of reasons this is not enough. Also, while on supported platforms it will update your PWA, it will do so silently. I needed a way to do all of the following:
- Control or initiate the update process
- Set my own versioning
- Notify users about updates
- Perform actions related to the update
- Work around platform and browser limitations and inconsistencies so that it works the same everywhere and without failure
-
@carlitos thank you man, you save a lot of time
-
Hi guys, I have a doubt, how does the browser know there is a new update? is the package-lock.json?
just changing this:
{
“name”: “app”,
“version”: “0.0.1”,is enough to automatically update on android/desktop browsers/installed versions?
-
@zeppelinexpress when you build your project (for a PWA) there will be new cache signatures, which a PWA should read and then force an update. However, as mentioned above this is inconsistent across platforms and I have found it necessary to make my app check a version string that I set on the server and call the update mechanisms at that point which works everywhere. See the code examples above for what I use to make this work.
-
Rejoining this thread, because I’ve figured out a complete solution for a user controlled app refresh (similar to the pop-up in the Quasar documentation) that might be useful for others who have the same issue. It’s a combination of ideas taken from the Workbox Advanced Recipes (https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offer_a_page_reload_for_users) and an article by Doug Allrich. It uses the Workbox-Window plug-in from Google.
<template> <q-dialog v-model="displayUpdatePrompt"> <q-banner> A new version is available. <template v-slot:action> <q-btn label="Update" @click="refreshApp" /> </template> </q-banner> <q-dialog> </template> <script> import { Wokbox } from 'workbox-window' export default { data() { return { displayUpdatePrompt: false, workbox: null, } }, methods: { refreshApp() { this.workbox.addEventListener('controlling', () => { window.location.reload() }) this.workbox.messageSkipWaiting() } }, created() { if ('service-worker' in navigator) { this.workbox = new Workbox('/service-worker.js') this.workbox.addEventListener('waiting', (event) => { this.displayUpdatePrompt = true }) this.workbox.register() } } } </script>
-
@Chris-0 awesome! would you add this as a component and then call it from App.vue or is there a best practice for where to position this in a Quasar project ?