How do I get my cached data from service worker in to the vuex store in offline mode?
The app works in offline mode in the browser, but when I add the site to home screen with the manifest.json file included, it won't show the cached data and only the general js, css and html.
I'm having a hard time figuring out, how I get my cached data from my PWA. I already have the data cached. The problem is retrieving it back to the vuex store state called "games", to display when the app is offline.
The vue.config.js code for caching the api call for the service worker.
module.exports = {
pwa: {
workboxPluginMode: "GenerateSW",
workboxOptions: {
navigateFallback: "/index.html",
runtimeCaching: [{
urlPattern: new RegExp('API_URL'),
handler: 'networkFirst',
options: {
networkTimeoutSeconds: 20,
cacheName: 'api-cache',
cacheableResponse: {
statuses: [0, 200],
},
},
}]
}
}
};
As you can see in the image above the code, it has stored the cache "api-cache" with the objects from the API.
Now I want to use this data in the cache from api-cache on my site, when the site is offline.
So my question is:
How do I get my cached data from service worker in to the vuex store in offline mode, when a user has added the app to their home screen?
You're thinking about this in the wrong way. You don't need the service worker to do anything for you. It's already doing it by providing you with a cache implementation. Instead, you need to use the navigator.onLine hook to determine if they have internet access. If not, then hydrate your store from the cache and make sure to subscribe to any mutations and push the state back into the cache, like this:
if (!navigator.onLine) {
const state = hydrateFromCache()
store.subscribe((mutation, state) => cache.setItems(state)
}
Where the hydrateFromCache method simply pulls the store in from the cache and hydrates the state of all vuex modules.
Related
I want to periodically call an API from my service worker to send data stored in the localStorage. This data will be produced and saved in localStorage when a user browses my website. Consider it something like saving stats in localStorage and sending it periodically through the service worker. How should I do this? I understand that I can't access localStorage from the service worker and will have to use the postMessage API. Any help would be highly appreciated.
You cannot access localStorage (and also sessionStorage) from a webworker process, they result will be undefined, this is for security reasons.
You need to use postMessage() back to the Worker's originating code, and have that code store the data in localStorage.
You should use localStorage.setItem() and localStorage.getItem() to save and get data from local storage.
More info:
Worker.postMessage()
Window.localStorage
Pseudo code below, hoping it gets you started:
// include your worker
var myWorker = new Worker('YourWorker.js'),
data,
changeData = function() {
// save data to local storage
localStorage.setItem('data', (new Date).getTime().toString());
// get data from local storage
data = localStorage.getItem('data');
sendToWorker();
},
sendToWorker = function() {
// send data to your worker
myWorker.postMessage({
data: data
});
};
setInterval(changeData, 1000)
Broadcast Channel API is easier
There are several ways to communicate between the client and the controlling service worker, but localStorage is not one of them.
IndexedDB is, but this might be an overkill for a PWA that by all means should remain slim.
Of all means, the Broadcast Channel API results the easiest. It is by far much easier to implement than above-mentioned postMessage() with the MessageChannel API.
Here is how broadcasting works
Define a new broadcasting channel in both the service worker and the client.
const channel4Broadcast = new BroadcastChannel('channel4');
To send a broadcast message in either the worker or the client:
channel4Broadcast.postMessage({key: value});
To receive a broadcast message in either the worker or the client:
channel4Broadcast.onmessage = (event) => {
value = event.data.key;
}
I've been using this package called localforage that provides a localStorage-like interface that wraps around IndexedDB. https://github.com/localForage/localForage
You can then import it by placing it in your public directory, so it is served by your webserver, and then calling: self.importScripts('localforage.js'); within your service worker.
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers says
Note: localStorage works in a similar way to service worker cache, but it is synchronous, so not allowed in service workers.
Note: IndexedDB can be used inside a service worker for data storage if you require it.
Also there is a bit of discussion here: How do I access the local storage using service workers in angular?
Stumbling over this question myself for a tiny webapp-project, I considered the following solution:
When the user is online, the data can be sent immediately. When he is offline, I use the SyncEvent.tag property to send information from the client to the serviceworker. Like this:
//offline_page.html (loads only, when user is offline)
button.onclick = function() {
//on click: store new value in localStorage and prepare new value for synchronization
localStorage.setItem("actual", numberField.value);
navigator.serviceWorker.ready.then(function(swRegistration) {
return swRegistration.sync.register('newval:'+numberField.value);
});
}
//sw.js
self.addEventListener('sync', function(event) {
//let's say everything in front of ':' is the option, everything afterwards is the value
let option = event.tag.replace(/(.*?)\:.*?$/, "$1");
let value = event.tag.replace(/.*?\:(.*?)$/, "$1");
if(option == "newval") {
event.waitUntil(
fetch("update.php?newval="+value)
.then(function(response) {
console.log(response);
})
);
}
});
update.php saves the new value to backend, as soon as the user goes online.
This won't give the service worker access to the localStorage, but it will send him every change made.
Just starting to get used to this syncing topic. So I would really be interested, wheather this is helpful and what others think about this solution.
I have an app using Nuxt on the front-end. Since Nuxt is a SSR technology the data in vuex stores gets deleted after refreshing the browser. Is there a way to save and keep vuex data in browser storage (preferably not cookies) even after refreshing the browser?
We use vuex-persist to save some store information in indexDB to provide offline feature (PWA) but you can use localstorage and cookies as well. Its pretty forward to use and also have an nuxt implementation:
// Inside - nuxt.config.js
export default {
plugins: [
{ src: '~/plugins/vuex-persist', ssr: false }
]
}
// ~/plugins/vuex-persist.js
import VuexPersistence from 'vuex-persist'
export default ({ store }) => {
new VuexPersistence({
/* your options */
key: 'vuex', // The key to store the state on in the storage provider.
storage: window.localStorage, // or window.sessionStorage or localForage
}).plugin(store);
}
You cannot save your Vuex state without any kind of browser storage. Depending on what you need to store, you could use:
localStorage if it's some small information, some setting or alike
make an API call to your backend and repopulate your Vuex store accordingly (best solution, especially if you stored some JWT token in your localStorage)
IndexedDB API, never used it but it's some kind of light database
Still, storing your whole Vuex state is not a thing to do in the browser for various reasons (security, performance, principle etc...).
In Addition to kissu's answer. You can use replaceState to restore a saved state.
But you probably shouldn't, you can create a bug that your user can't get out of by refreshing. And that's really frustrating.
The pattern to do this is fairly simple.
First, in vuex, subscribe to all mutations
https://vuex.vuejs.org/api/#subscribe
const unsubscribe = store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
// you may call unsubscribe to stop the subscription
unsubscribe()
Check which mutations you are interested in saving and store them in localStorage, or IndexDb or whatever you want.
On page load you need to check your storage solution for this data. If it exists. call replaceState
https://vuex.vuejs.org/api/#replacestate
store.replaceState(state: Object) // give it an object structure matching your Vuex module state structure
And this is how you can save and reload state.
I am trying to integrate firebase auth in my nuxt pwa for offline use. But while running the app in offline mode it fails with a failed response from www.googleapis.com/getAccountInfo?key=... and securetoken.googleapis.com/token?key=.....
Isn't it suppose to cache that as well? Or is there any workaround for it.
Config in nuxt.config.js
services: {
auth: {
static:true,
persistence: 'local',
initialize: {
onAuthStateChangedMutation: 'ON_AUTH_STATE_CHANGED_MUTATION'
}
},
firestore: {
static:true
},
},
Updated error screenshot
When loading the page/app:
Firebase restores the authentication state from local persistence.
It then tries to verify that the authenticate state is still valid by calling the server.
If that call succeeds, it updates the local state.
If the call to the server could not be completed, it assumes the local state is still correct, at least until it can verify it.
So the call you're seeing is normal/expected, and should not interfere with the functioning of the application while it's offline.
I've been trying to make my Angular application work completely offline.
Meaning I want all of my requests to be cached including the POST requests on IndexedDB through a Service worker.
I have installed #angular/pwa package to implement offline functionality. So far the App is able to load when I'm offline and it's also caching all the GET requests from the application. But It's not caching the POST requests (which is expected).
So I tried implementing a custom Service worker to do my job.
I created 2 new files
sw-master.js
sw-sync.js
Inside sw-master.js
importScripts('./ngsw-worker.js');
importScripts('./sw-sync.js');
Inside sw-sync.js
(function () {
'use strict';
self.addEventListener('fetch', (event) => {
console.log(event.request);
});
});
Inside app.module.ts, instead of this
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
I Put
ServiceWorkerModule.register('sw-master.js', { enabled: environment.production }),
But the console.log inside the sw-sync.js -> fetch event is not working. I assume it's because there is a fetch event in ngsw-worker.js which has been generated by angular when compiled.
How can I solve this problem? Thanks in advance.
I use service worker with sw-toolbox library. My PWA caches everything except API queries (images, css, js, html). But what if some files will be changed someday. Or what if service-worker.js will be changed.
How application should know about changes in files?
My service-worker.js:
'use strict';
importScripts('./build/sw-toolbox.js');
self.toolbox.options.cache = {
name: 'ionic-cache'
};
// pre-cache our key assets
self.toolbox.precache(
[
'./build/main.js',
'./build/main.css',
'./build/polyfills.js',
'index.html',
'manifest.json'
]
);
// dynamically cache any other local assets
self.toolbox.router.any('/*', self.toolbox.cacheFirst);
// for any other requests go to the network, cache,
// and then only use that cached resource if your user goes offline
self.toolbox.router.default = self.toolbox.networkFirst;
I don't know what is the usual method to update cache in PWA. Maybe PWA should send AJAX request in background and check UI version?
AFAIK the sw_toolbox does not have a strategy for cache with network update. This is really what you want I think.
You want to modify the cache-network race strategy - > https://jakearchibald.com/2014/offline-cookbook/#cache-network-race
Instead of just letting the loser fade away, once the network responds you will want to update the client. This is a little more advanced that I have time or time to explain here.
I would post a message to the client to let it know there is an update. You may want to alert the user to the update or just force the update.
I don't consider this to be an edge case, but a very common, but advanced scenario. I hope to publish a more detailed solution soon.
There is nice solution written here where he states (in a nutshell) to either not use cache-first strategy or update a UX pattern of displaying a "Reload for the latest updates."
I dealt with services workers without using any library and the solution I ended up coming up with involved a bit of server side code and some client side. The strategy in a nutshell
Firstly the variables you will need and where:
On the server side have a "service worker version" variable (Put this in a database or config file if you are using something like php that will update immediately on the server side without requiring a redeploy. Let's call it serverSWVersion
On one of the javascript files you cache (I have a javascript file dedicated to this) have a global variable that will also be the "service worker version". Let's call it clientSWVersion
Now how to use the two:
Whenever a person lands on the page make an ajax call to your server to get the serverSWVersion value. Compare this with the clientSWVersion value.
If the values are different that means your web app version is not
the latest.
If this is the case then unregister the service worker and refresh the page so that the service worker will be re registered and the new files will be cached.
What to actually do when new file is available
Update the serviceSWVersion and clientSWVersion variables and upload to server where applicable.
When a person visits again then the service worker should be re registered and all the cached files will be retrieved.
I have provided a php server side based code that I used while I was implementing this strategy. It should show you the principles. Just drop the "Exercise" folder in a htdocs of a php server and it should work without you having to do anything else. I hope you find it useful... And remember you could just use a database instead of a config file to store the server side service worker variable if you are using some other server instead of php:
Zip file with code:
ServiceWorkerExercise.zip
When a service worker is altered, the browser will install it, but the new version will not be activated until the browser tab or PWA app window is closed and re-opened.
So, if you change the cache name, the new cache will not serve any files until the browser re-opens, nor will the old cache be deleted until that time.
You can detect service worker changes in your page javascript using registration.onupdatefound and ask the user to close and re-open the window - something like this:
// register the service worker
navigator.serviceWorker.register('sw.js').then(function(registration)
{
registration.onupdatefound = function()
{
console.log("ServiceWorker update found.");
alert("A new version is available - please close this browser tab or app window and re-open to update ... ");
}
}, function(err)
{
console.log('ServiceWorker registration failed: ', err);
});
change self.toolbox.router.any('/', self.toolbox.cacheFirst); to self.toolbox.router.any('/', self.toolbox.fastest);