How to make a service worker work offline? - javascript

I can't get my service worker to work offline. No matter what tutorial I use.
I registered the service worker in my index.html file like:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/serviceworker.js')
.then((reg) => console.log('success: ', reg.scope))
.catch((err) => console.log('Failure: ', err))
})
}
</script>
The serviceworker.js looks like:
const CACHE_NAME = "version-1"
const urlsToCache = [ 'index.html' ]
const self = this
// Install Service Worker
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Opened cache')
return cache.addAll(urlsToCache)
})
)
})
// Activate Service Worker
self.addEventListener('activate', (event) => {
const cacheWhitelist = []
cacheWhitelist.push(CACHE_NAME)
event.waitUntil(
caches.keys().then((cacheNames) => Promise.all(
cacheNames.map((cacheName) => {
if(!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName)
}
})
))
)
})
I'm not sure what I've forgotten or what mistake I have. The serviceworker.js is right beside the index.html, manifest.json etc.
I sometimes get an error with "An unknown error occured when fetching the script".
Kind regards

Your service worker only contains code to create a cache and store the HTML file in that local cache. This cache is managed by you, the browser does not care about it when fetching web pages normally.
When your browser fetches that index.html web page, it does not know about that cache. So what you need to do is to intercept that fetch. For this, you need to register an event listener for the fetch event and respond with your cache. The browser will then use that file instead of sending a request to the server.
self.addEventListener('fetch', event => {
if (event.request.method != 'GET') return;
event.respondWith(async function() {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(event.request);
// If no cached version, fall back to server fetch
return cached ? cached : fetch(event.request);
})
});

Related

How to force update PWA cache after login [JS]?

I have a simple forum that I want to make work offline. I have a dynamic and static cahce. The static cache fills on the install event and the dynamic as you go allong and look at posts.
The problem is that the pages it caches include the header where you have a link to the profile if you are logged in and link to registration page if you are not logged in.
After logging in it still shows the registration link instead of the profile link. The way to fix it would be to refresh the cache?
Is there a way to do this or is there some other fix for this type of issue(besides network first approach)?
I am relativly new to PWAs and I can't find any useful tips.
My service worker looks like this:
const staticCacheName = "ScroocCacheV1";
const dynamicCacheName = "ScroocDynamicCacheV1";
const assets = [
'/',
'/css/main_styles.css',
'/js/ui.js',
'/about',
'/policies',
'/register',
'/createTopic',
'/stats',
'/proposals',
];
const limitCacheSize = (name, size) => {
caches.open(name).then(cache => {
cache.keys().then(keys => {
if(keys.length > size) {
cache.delete(keys[0]).then(limitCacheSize(name, size));
}
});
});
}
const dynamicCacheLimit = 18;
// Install service worker
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(staticCacheName).then(cache => {
cache.addAll(assets);
})
);
});
// Activate event
self.addEventListener('activate', evt => {
evt.waitUntil(
caches.keys().then(keys => {
keys.map((key => {
if (key !== staticCacheName && key !== dynamicCacheName) {
return caches.delete(key); //Deleting the old cache (cache v1)
}
}))
})
)
});
// Intercept fetch
self.addEventListener('fetch', evt => {
evt.respondWith(
fetch(evt.request).then(fetchRes => {
return caches.open(dynamicCacheName).then(cache => {
return caches.match(evt.request).then(function(result) {
if (result) {
return result;
} else {
cache.put(evt.request.url, fetchRes.clone());
limitCacheSize(dynamicCacheName, dynamicCacheLimit);
return fetchRes;
}
});
});
}).catch(function() {
return caches.match(evt.request).catch((error) => {
console.log(error)
return caches.match('/img/fallbackImage.png');
});
})
);
});
This worked for me!
Before you could attempt to empty the cache, the service worker must first be successfully installed. So for the record, your sw.js file should begin with the usual
self.addEventListener("install", ...etc
Now this is where we get to cleaning up. Create a variable to store the name of the cache you wish to purge/update (makes targeting different caches easier)
var TargetCache= 'NameOfCacheToClean';
Next, add an EventListener that triggers each time the service worker is activated (the activate-event occurs on page reload/refresh)
self.addEventListener('activate', event =>
{
const currentCaches = [TargetCache];
event.waitUntil
(
caches.keys()
.then(cacheNames => {return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));})
.then(cachesToDelete => {return Promise.all(cachesToDelete.map(cacheToDelete => {return caches.delete(cacheToDelete);})); })
.then(() => self.clients.claim())
);
});
Just in-case, i normally add the event listener that intercepts the outgoing fetch-requests after the code that clears the old cache.
self.addEventListener('fetch', function(event) {...etc
The way to fix it would be to refresh the cache?
That is correct, assuming you cached the path /login, the service worker will always display what was cached under that path, based on your code.
Is there a way to do this or is there some other fix for this type of issue(besides network first approach)?
It's not really something to "fix", what you described is somewhat expected behaviour.
There are several ways around this tho, network first is just one:
use a message to update cache on login
use different urls or url parts like query to skip cache when user is logged in
hide the UI that you don't need on the client depending on user state
Probably many more.

reload cashed files to browser serviceWorker in JavaScript

Every time that I edit my PWA App source, Like html or js files, I have to manually delete browser cached files and history in order to load new data that i uploaded.
my serviceWorker routine try to cache my main url html only but it seems all files being cached automatically.
here is my serviceWorker file:
var cacheName = 'app-pwa';
var filesToCache = [
'/',
'/index.html'
];
/* Start the service worker and cache all of the app's content */
self.addEventListener('install', function(e) {
e.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
/* Serve cached content when offline */
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
So anyone can help me how to refresh cached files in browser?
thank you
well I find the way myself and will share here for anyone who may need it.
the way is to define versions in the end of cache file name:
var cacheName = 'app-pwa-v1';
and increase version for any update. and define another service worker to delete previous cached files.
self.addEventListener('activate', (e) => {
e.waitUntil(caches.keys().then((keyList) => {
Promise.all(keyList.map((key) => {
if (key === cacheName) { return; }
caches.delete(key);
}))
})));
});
this will delete old cached files after second app startup.

Manifest Error: Page Does not Work Offline

I am trying to make Progressive Web App. I have created the sw.js (serviceworker) file and registered it successfully but It is not working still. The Error Given Is:
Page Does not work offline.
The code of my sw.js is given below.
const cacheName='whitecabs'
const staticAssets = [
'/404.html'
'/bookingSubmit.js'
'/bookQuote.js'
'/head.css'
'/index.html'
]
self.addEventListener("install", function e => {
const cache = await caches.open(cacheName)
await cache.addAll(staticAssets)
return self.skipWaiting();
});
self.addEventListener('activate', e =>{
self.clients.claim();
})
Why the error is coming, what does it actually mean and how can I solve it?
Hope For Reply Soon.
Thanks for help in advance.
There were several errors in the sw.js file. such as there is no commas in the staticAssest array, then await is the only async function and so on. After working on it and writing the code again from the beginning I was able to solve the problem. I am giving the correct code below. I Think This Will Help Other Who Are Trying To Make A Web App.
const staticCacheName='whitecabcache'
const assets=[
'/',
'/404.html',
'/app.js',
'/bookingSubmit.js',
'/bookQuote.js',
'/head.css',
'/index.html',
'/locations.html',
'/locationStyle.css',
'/manaliPackages.html',
'/menu.gif',
'/shimlaPackages.html',
'/style.css'
]
//install event
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(staticCacheName).then( cache => {
console.log('caching cell assets');
cache.addAll(assets)
})
)
})
//activate event
self.addEventListener('activate', evt => {
console.log('service worker activated');
})
//fetch event
self.addEventListener('fetch', evt => {
console.log('fetch Event', evt);
})

PWA - empty service worker precache runtime

I'm developing an audio-based PWA and, since I'm not familiar with this technology, I have a couple of doubts regading the cache management and invalidation in the service worker.
The application need to work offline, that I covered using a SW precache.
My only doubt is the amount of data: in the experience there are 5 use case scenarios. Each scenario has ~30MB of audio content, that means around 150MB + all images, js and css in total to precache.
I know that this exceeds the limit of some browsers (se this question and this article)
and in general you must be careful with the storage size, that also depends on the user's device available space on disk.
So that's what I thought: since between one scenario and another, the users will stop by a desk with WiFi connection, my idea is to empty the cache runtime after an user's action (like pressing a button), and replace it with thw new content.
This way I would store only one scenario at a time, that means ~35MB, a reasonable size.
Do you think that's a good approach?
What's the best way to implement this?
Here's my current code:
service-worker.js
const PRECACHE = 'precache-test-v1';
// A list of local resources we always want to be cached.
const PRECACHE_URLS = [
'/',
'/audio/scenario1.mp3',
'/audio/scenario2.mp3',
'/audio/scenario3.mp3',
'/audio/scenario4.mp3',
'/audio/scenario5.mp3',
'/css/style.css',
'/js/bundle.js',
'/img/favicon.png',
'/img/logo.png',
'/img/image1.png',
'/img/image2.png',
'/img/image3.png',
'/img/image4.png',
'/img/image5.png',
];
// never cache these resources
const TO_SKIP = [/* empty for now */];
// The install handler takes care of precaching the resources we always need.
self.addEventListener('install', event => {
const now = new Date();
console.log(`PWA Service Worker installing - :: ${now} ::`);
event.waitUntil(caches.open(PRECACHE).then(cache => {
return cache.addAll(PRECACHE_URLS).then(() => {
self.skipWaiting();
});
}));
});
// The activate handler takes care of cleaning up old caches.
self.addEventListener('activate', event => {
const now = new Date();
console.log(`PWA Service Worker activating - :: ${now} ::`);
const currentCaches = [PRECACHE];
event.waitUntil(
caches.keys().then(cacheNames => {
return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
}).then(cachesToDelete => {
return Promise.all(cachesToDelete.map(cacheToDelete => {
return caches.delete(cacheToDelete);
}));
}).then(() => self.clients.claim())
);
});
// The fetch handler serves responses for same-origin resources from a cache.
self.addEventListener('fetch', event => {
// Skip cross-origin requests, like those for Google Analytics and the other provided urls.
if (event.request.url.startsWith(self.location.origin) && TO_SKIP.every(url => !event.request.url.includes(url))) {
event.respondWith(
caches.match(event.request).then(resp => {
return resp || fetch(event.request).then(response => {
return caches.open(PRECACHE).then(cache => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
}
});
index.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(registration => {
console.log('Registration successful, scope is:', registration.scope);
}).catch(error => {
console.log('Service worker registration failed, error:', error);
});
}
Thank you for your time,
Francesco
Hmm.. instead of precaching 5 videos, you could provide an button Save for offline so that the user can save only that videos that he wants to see later offline:
let videoUrl = url to that video:
button.addEventListener('click', function(event) {
event.preventDefault();
caches.open("myVideoCache").then(function(cache) {
fetch(videoUrl)
.then(function(video) {
cache.add(video);
});
});
});
Do delete 1 entry you need to open your cache and delete it. Pass the path that you stored.
caches.open('myVideoCache').then(function(cache) {
cache.delete('/path/to/audio.mp4').then(function(response) {
console.log("entry deleted");
});
})
More details you can find here: https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker

How to fetch data from firebase for PWA

I want to create a PWA in which i am getting data from firebase. Once the page is loaded ADS( in form of objects ) are retrieved from firebase and then displayed. What i want is that once page is loaded if user has lost the connection or is offline, the cache stores all objects that are once loaded at time user was having connection and made them available for offline use i.e without connection to internet.
Link to Download JSON FILE of firebase data
and here is the code i am using in service worker
var cacheName = 'test me';
const filesToCache = [
'./',
'home.html',
'details.html',
'/css/main.css',
'/css/home.css',
'/images/nokia.png',
'/js/app.js',
'/js/main.js',
'/js/home.js'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1')
.then(res => {
console.log('wait.........!')
return res.addAll(filesToCache);
})
);
console.log('installed');
});
self.addEventListener('activate', (event) => {
console.log('activated');
});
You can use the CacheAPI for that:
const url = '/myAPI/endpoint'
fetch(url).then((res) => {
return caches.open('v1').then((cache) => {
return cache.put(url, res)
})
})
To retrive it:
caches.open('v1').then((cache) => {
cache.match('/myAPI/endpoint').then((res) => {
//res is the Response Object
})
})
You can find a lot more here: https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker

Categories