[Solved] PWA force refresh when new version released?
-
It is really a shame. And a search around the web has very little information of this type (ie clear descriptions of what is and is not supported), and no word from Apple on when or if they will ever support the missing pieces. My guess is they will not unless there is a huge outcry, for exactly the reasons you mention (ie they won’t make money from it).
-
OMFG!!! I have a solution!!!
- I add a check for versionstring on the server, and return it to the app.
- I look for it in localtstorage (IndexedDB) and if I don’t find it, I add it. If I do find it, I compare versions and if there is a newer one on the server, I throw up a dialog.
- Dismissing this dialog (my ok button is labeled “update”) runs
window.location.reload(true)
and then stores the new versionstring in localstorage
RESULT: My app is updated!! I have been searching for a way to do this for weeks and weeks, so glad it finally works on iOS. Hope this helps someone else.
-
@ssuess thank you for add your solution, I have implemented it with Vuex and works fine.
-
@ssuess @israel965 can you post a bit more detail please? I’m having the exact same problem, but I haven’t worked at that lowlevel with quasar yet.
If i wanted to just add a button to manually force update, without the version check… (I would have to tell users to push it of course but…)
window.location.reload(true)
this is the magic code that actually busts the cache and reloads everything?
-
do you manually create a version string each time you do a new version deploy somewhere or does the quasar build / webpack process generate some type of hash you can use easily?
-
the localStorage method you use, does quasar wrap these APIs? if using VueX does vueX have an option to push data to localStorage?
Thanks for any help.
-
-
@dcsan
window.location.reload(true)
will clear the cache, but unless you have the updated version up on the server, it will just reload the same thing. The fact of publishing a new version in quasar (and then uploading it to the server) sets all the unique cache signatures. While on android the PWAs will automatically update when those signatures change, on iOS this was not the case, because the app would never check. So runningwindow.location.reload(true)
forces the app to delete its cache and check again, this time finding the new version (and loading and caching it). As for the localStorage method I use, it is via the localForage plugin (https://github.com/shidianxia/vue-localforage). I just store a simple versionstring in my localStorage and compare that against one I set on the server and retrieve each time I update (this is separate from the cache signatures that Quasar sets within the code). If the version on the server is newer, I clear my local cache and reload (Otherwise I leave it alone because I don’t want to needlessly reload my app all the time, only when there is a newer version). Hope this helps. -
thanks for the response! and tip on localForage, tho it’s a callback based API which I don’t really prefer.
I also found this writeup
https://deanhume.com/displaying-a-new-version-available-progressive-web-app/are there any version info in the app available from the quasar webpack build process? otherwise I’m thinking to put something into my deploy pipeline to write a JSON file with the current epoch time into /public folder and then check against that. no need to put that into the DB just deploy the file and load from client as a static JSON.
-
localForage can be used via promise or callback, FYI.
-
Quasar also provides a localstorage API:
https://quasar-framework.org/components/web-storage.html -
After this time, is there a way in Quasar v1.X to auto-refresh changes if detected or show user a notification? Using service-workers or another tool.
-
As mentioned in other posts, I’ve solved this adding the next code to the
register-server-worker.js
:updated (registration) { // registration -> a ServiceWorkerRegistration instance console.log('New content is available; please refresh.') Notify.create({ message: i18n.t('messages.update_available'), icon: 'cloud_download', closeBtn: i18n.t('labels.update'), timeout: 10000, onDismiss () { location.reload(true) } }) }
But as you want to update immediately, you must take control of the service worker lifecycle using
skipWaiting()
.
Add the next code to yourcustom-service-worker.js
if you are using one like me:workbox.core.skipWaiting();
But, if you are using the default configuration, add the next to the
quasar.conf.js
file that will add for you:pwa: { workboxOptions: { skipWaiting: true }, // only for NON InjectManifest }
If you don’t add this skip, your app will enter in a endless loop asking for update if you try to reload.
-
@Carlitos Wow! Great! Thank you very much! I tried the same in the past and I thought it wasn’t the right way to do so because of the endless loop. I didn’t heard about this skipWaiting property. Now it’s working flawlessly.
Just one thing, how did you manage to use translations inside
register-server-worker.js
file? I couldn’t figure out how to import Quasar translations. -
@kidox You only must export the instance from your i18n boot file and import where you need. This article will help you.
-
@Carlitos solved. Thank you!
-
@Carlitos when I add
workboxOptions: { skipWaiting: true }
chrome throws an error:Uncaught TypeError: workbox.core.skipWaiting is not a function
Any ideas?
-
Actually I see the problem above, and have fixed it with a custom service worker file:
workboxOptions: { skipWaiting: true }
unfortunately results in aworkbox.core.skipWaiting()
being placed in the service worker file. This is incorrect. The correct call should beworkbox.skipWaiting()
. When I put that in a custom service worker file, it works. I will report this as a bug in Github, but FYI for everyone here. -
OK, the problem was actually that despite updating quasar to all the latest and greatest with each new update, something was corrupt because it was still embedding the 3.6.3 version of workbox scripts from google instead of the current 4.3.1. Completely creating a new quasar project folder and moving all my files into it seems to have fixed it. Now
workboxOptions: { skipWaiting: true }
works without an error. -
I have been playing around with the service worker logic for days now, and I can’t find a way to actually trigger the service worker to check for an update other than attaching a
window.location.reload(true)
to a button that the user presses. Does anyone have a way other than this to get the service worker to check for changes? On iOS, it seems to do it at quite random times, so I might have an new version on the server but the service worker won’t know or check for many hours. Once it finally does, I can use the update mechanism above, but I would like to be able to force it to check without reloading the window (which when there is an update causes a double reload in my app). -
-
Thanks, but the second link you sent is actually something I wrote the answer to myself, and it doesn’t address the service worker problem, it is a bit of a hacky workaround, and the most important problem with it is that it does not always reload all of the contents (because the service worker is holding on to the current context). That is why I am looking for a way to actually trigger the service worker itself to check. I am not sure if the bug is in the
register-service-worker
npm that quasar uses for this purpose, in quasar code itself, or something in my implementation. This is a punishingly frustrating thing to try to troubleshoot. There should be a way to force the service worker to check for an update, and then perform whatever actions are specified in theregister-service-worker.js
file. Currently the only way I can find to force that is with awindow.location.reload(true)
as mentioned above, but it suffers from the need to have the user trigger it and the dual reload problem if there is in fact an update. -
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.