I have implemented the code below to clear service worker cache and reloads - after the user has accepted update of the service worker. The code works well in Chrome and Edge, but Firefox will not reload the page. Firefox will keep asking to install the same version until I hard refresh (shift reload) the page.
service-worker-base.js
// Imports
const CACHE_DYNAMIC_NAME = 'DEBUG-035'
setCacheNameDetails({ prefix: 'myApp', suffix: CACHE_DYNAMIC_NAME });
// Cache then network for css
registerRoute(
'/dist/main.css',
new StaleWhileRevalidate({
cacheName: `${CACHE_DYNAMIC_NAME}-css`,
plugins: [
new ExpirationPlugin({
maxEntries: 10, // Only cache 10 requests.
maxAgeSeconds: 60 * 60 * 24 * 7 // Only cache requests for 7 days
})
]
})
)
// Cache then network for images
//...
// Use a stale-while-revalidate strategy for all other requests.
setDefaultHandler(new StaleWhileRevalidate())
precacheAndRoute(self.__WB_MANIFEST)
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
// Clear cache before installing new service worker
self.addEventListener('activate', (event) => {
var cachesToKeep = ['none'];
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if (cachesToKeep.indexOf(key) === -1) {
console.log('Delete cache', key)
return caches.delete(key);
}
}));
})
);
event.waitUntil(self.clients.claim());
});
//...
app.js
const enableServiceWorker = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'qa'
const serviceWorkerAvailable = ('serviceWorker' in navigator) ? true : false
if (enableServiceWorker && serviceWorkerAvailable) {
const wb = new Workbox('/service-worker.js');
let registration;
const showSkipWaitingPrompt = (event) => {
if (window.confirm("New version available! Refresh?")) {
wb.addEventListener('controlling', (event) => {
window.location.reload();
});
console.log('registration', registration) //<-- LINE 13
// In Chrome and Edge this logs a service worker registration object
// In Firefox, this is undefined !!?
if (registration && registration.waiting) {
messageSW(registration.waiting, {type: 'SKIP_WAITING'});
}
}
}
// Add an event listener to detect when the registered service worker has installed but is waiting to activate.
wb.addEventListener('waiting', showSkipWaitingPrompt);
wb.addEventListener('externalwaiting', showSkipWaitingPrompt);
wb.register().then((r) => {
registration = r
console.log('Service worker registered', registration) //<-- LINE 23
}).catch(registrationError => {
console.error('Service worker error', registrationError )
})
}
// Install prompt event handler
export let deferredPrompt
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault() // Prevent Chrome 76 and later from showing the mini-infobar
deferredPrompt = event // Stash the event so it can be triggered later.
// Update UI notify the user they can add to home screen
try{
showInstallPromotion()
}catch(e){
// console.log('showInstallPromotion()', e)
}
})
window.addEventListener('appinstalled', (event) => {
console.log('a2hs installed')
})
In Firefox dev-tools I can see the new service worker precache, but all other cache belongs to previous version. After shift-reload the new service worker gets "fully activated".
How can I get Firefox to hard reload the page automatically after new service worker install?
UPDATE: It seems like Firefox is missing a handle to the service worker on line 13 of app-js.
UPDATE: Console output indicates that the code sequence differs between browsers?
Chrome / Edge
registration > ServiceWorkerRegistration {installing: null, waiting: ServiceWorker, active: ServiceWorker, navigationPreload: NavigationPreloadManager, scope: "http://127.0.0.1:8080/", …} app.js:13
**PAGE RELOAD***
Service worker registered ServiceWorkerRegistration {installing: null, waiting: null, active: ServiceWorker, navigationPreload: NavigationPreloadManager, scope: "http://127.0.0.1:8080/", …} app.js:23
Firefox
registration undefined app.js:13:14
Service worker registered > ServiceWorkerRegistration { installing: null, waiting: ServiceWorker, active: ServiceWorker, scope: "http://127.0.0.1:8080/", updateViaCache: "imports", onupdatefound: null, pushManager: PushManager } app.js:23:12
Kind regards /K
This might help you , please check controllerchange of serviceworker.
As per this documentations:- The oncontrollerchange property of the ServiceWorkerContainer interface is an event handler fired whenever a "controllerchange event occurs" — when the document's associated ServiceWorkerRegistration acquires a new active worker.
To use it, you can attach an event handler and it will be triggered only when a new service worker activates. and If you want you can reload the page using reload function.
navigator.serviceWorker.addEventListener('controllerchange', function(){
window.location.reload();
});
I created a special case since Firefox seems to install the new service-worker differently from chromium (does not have a handle to the service-worker registration on line 13)
When the new service worker is waiting showSkipWaitingPrompt gets triggered and
in Chromium the service-worker registration is ready ---> we call SKIP_WAITING --> the browser reloads and replaces the service worker
in Firefox the service-worker registration handle is not accessible yet --> we cannot call SKIP_WAITING
The solution, for me, was to add the below line in the registration. This tells Firefox to skip waiting when the new service-worker is in waiting state and we have a registration handle.
wb.register().then((r) => {
registration = r
if(registration.waiting){ mySkipWaitingNow() } // Fix for firefox
...
The mySkipWaitingNow() tells the service-worker to SKIP_WAITING without prompting the user.
This will never trigger in Chrome/Edge since the browser reloads in showSkipWaitingPrompt() - see point 1 above.
To prevent a possible eternal loop I also created a global variable skipWaitingConfirmed that gets set in showSkipWaitingPrompt() and checked in mySkipWaitingNow().
/K
Related
I try to add some routes in cache using message event.
On every page, there are several on dynamic that I would like to keep in cache. For this, i send an array of URL to my Service Worker on document load :
window.addEventListener('load', () => {
if (serviceWorker.isServiceWorkerSupported()) {
serviceWorker.register();
if (typeof PRECACHE_ROUTES !== 'undefined') {
serviceWorker.sendPreCacheRoutesToSW(PRECACHE_ROUTES);
}
}
});
But, with this method, if user have no network, the StaleWhileRevalidate same not work, you can see an example :
registerRoute(
'/',
new StaleWhileRevalidate({
cacheName: 'routes', // Work on offline
plugins,
}),
);
self.addEventListener('message', event => {
if (event.data && event.data.type === 'PRECACHE_ROUTES') {
event.data.routes.forEach(route => {
registerRoute(
route,
new StaleWhileRevalidate({
cacheName: 'routes', // Not work on offline
}),
);
});
event.waitUntil(
caches.open('routes').then(cache => cache.addAll(event.data.routes)),
);
}
});
All urls are well cached, but do not seem to be taken into account offline.
Anyone can help me ?
I would suggest following this recipe using workbox-window and workbox-routing to accomplish that:
// From within your web page, using workbox-window:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', (event) => {
// Get the current page URL + all resources the page loaded.
// Replace with a list of URLs obtained elsewhere, as needed.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map((r) => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
This will automatically apply the routes defined in your service worker to the URLs you provide in the payload.
Dynamically setting up routes for those URLs inside of your message event isn't going to give you the behavior you're after, as you've found.
Using Workbox in a service worker in a javascript webapp.
Want to clear the entire workbox/application cache of all content... basically go back to a state as similar as possible to the state before first load of the app into a browser, potentially to be followed by refreshing via window.location.href = '/'.
Googling and looking on SO, I have found various recipes for clearing various things from the cache. I have not been able to figure out a simple way to just 'clear everything and start over'.
I tried this in server code in sw.js:
var logit = true;
if (logit) console.log('Service Worker Starting Up for Caching... Hello from sw.js');
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.1/workbox-sw.js');
if (workbox) {
if (logit) console.log(`Yay! Workbox is loaded 🎉`);
} else {
if (logit) console.log(`Boo! Workbox didn't load 😬`);
}
workbox.routing.registerRoute(
// Cache image files
/.*\.(?:mp3|flac|png|gif|jpg|jpeg|svg|mp4)/,
// Use the cache if it's available
workbox.strategies.cacheFirst({
// Use a custom cache name
cacheName: 'asset-cache',
plugins: [
new workbox.expiration.Plugin({
// Cache only 20 images
maxEntries: 20,
// Cache for a maximum of x days
maxAgeSeconds: 3 * 24 * 60 * 60,
})
],
})
);
self.addEventListener('message', function(event){
msg = event.data;
console.log("SW Received Message: " + msg);
if (msg==='clearCache') {
console.log('Clearing Workbox Cache.');
WorkBoxCache = new workbox.expiration.Plugin;
WorkBoxCache.expirationPlugin.deleteCacheAndMetadata();
//WorkBoxCacheServer.clear();
}
});
paired with this on the client:
navigator.serviceWorker.controller.postMessage("clearCache");
This didn't work, though the message was apparently passed. Also, this seems an inelegant solution and I presume there is a simpler one.
How can this be done?
How can it be initiated from the client side in client side js on the browser? What does this require in server side code (eg in sw.js).
Thank you
CacheStorage is accessible in the client code (where you register the SW) so you can delete it from there.
caches.keys().then(cacheNames => {
cacheNames.forEach(cacheName => {
caches.delete(cacheName);
});
});
If we only delete the cache then it damage service worker ,service worker will not work properly, so we have to unregister service worker then have to delete cache and then reregister service worker.
refreshCacheAndReload = () => {
if ('serviceWorker' in navigator) {
serviceWorkerRegistration.unregister();
caches.keys().then(cacheNames => {
cacheNames.forEach(cacheName => {
caches.delete(cacheName);
});
}).then(() => {
serviceWorkerRegistration.register();
})
}
setTimeout(function () { window.location.replace(""); }, 300)
}
Demo video: https://www.youtube.com/watch?v=UBfnvx6jC_A
I followed along with Udacity's Offline Web Applications course in order to get my app working offline. Here is my code:
main.js
// other stuff above
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/service-worker.js').catch(function() {
console.log('Service worker registration failed.');
});
}
service-worker.js
let currCacheName = 'premium-poker-tools-1';
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(currCacheName).then(function(cache) {
let promise = cache.addAll([
'/',
'app.js',
// 'c7d016677eb7e912bc40.worker.js',
// 'f328c7e2b379df12fa4c.worker.js',
'static/logo.png',
'static/favicon.png',
'static/loading.svg',
'static/cards/ace-of-clubs.png',
'static/cards/king-of-clubs.png',
'static/cards/queen-of-clubs.png',
'static/cards/jack-of-clubs.png',
'static/cards/ten-of-clubs.png',
'static/cards/nine-of-clubs.png',
'static/cards/eight-of-clubs.png',
'static/cards/seven-of-clubs.png',
'static/cards/six-of-clubs.png',
'static/cards/five-of-clubs.png',
'static/cards/four-of-clubs.png',
'static/cards/three-of-clubs.png',
'static/cards/two-of-clubs.png',
'static/cards/ace-of-spades.png',
'static/cards/king-of-spades.png',
'static/cards/queen-of-spades.png',
'static/cards/jack-of-spades.png',
'static/cards/ten-of-spades.png',
'static/cards/nine-of-spades.png',
'static/cards/eight-of-spades.png',
'static/cards/seven-of-spades.png',
'static/cards/six-of-spades.png',
'static/cards/five-of-spades.png',
'static/cards/four-of-spades.png',
'static/cards/three-of-spades.png',
'static/cards/two-of-spades.png',
'static/cards/ace-of-hearts.png',
'static/cards/king-of-hearts.png',
'static/cards/queen-of-hearts.png',
'static/cards/jack-of-hearts.png',
'static/cards/ten-of-hearts.png',
'static/cards/nine-of-hearts.png',
'static/cards/eight-of-hearts.png',
'static/cards/seven-of-hearts.png',
'static/cards/six-of-hearts.png',
'static/cards/five-of-hearts.png',
'static/cards/four-of-hearts.png',
'static/cards/three-of-hearts.png',
'static/cards/two-of-hearts.png',
'static/cards/ace-of-diamonds.png',
'static/cards/king-of-diamonds.png',
'static/cards/queen-of-diamonds.png',
'static/cards/jack-of-diamonds.png',
'static/cards/ten-of-diamonds.png',
'static/cards/nine-of-diamonds.png',
'static/cards/eight-of-diamonds.png',
'static/cards/seven-of-diamonds.png',
'static/cards/six-of-diamonds.png',
'static/cards/five-of-diamonds.png',
'static/cards/four-of-diamonds.png',
'static/cards/three-of-diamonds.png',
'static/cards/two-of-diamonds.png',
'static/feedback/1.png',
'static/feedback/2.png',
'static/feedback/3.png',
'static/feedback/4.png',
'static/feedback/flop-selector.png',
'static/feedback/green-grid-squares.png',
'static/feedback/user-set-range-to-simulate-to-street.png',
'static/guides/beginners-guide/1.png',
'static/guides/beginners-guide/2.png',
'static/guides/beginners-guide/3.png',
'static/guides/beginners-guide/4.png',
'static/guides/beginners-guide/5.png',
'static/guides/beginners-guide/6.png',
'static/guides/beginners-guide/7.png',
'static/guides/beginners-guide/8.png',
'static/guides/beginners-guide/9.png',
'static/guides/beginners-guide/10.png',
'static/guides/beginners-guide/11.png',
'static/guides/beginners-guide/12.png',
'static/guides/beginners-guide/13.png',
'static/guides/beginners-guide/14.png',
'static/guides/beginners-guide/15.png',
'static/guides/beginners-guide/16.png',
'static/guides/beginners-guide/17.png',
'static/guides/beginners-guide/18.png',
'static/guides/beginners-guide/19.png',
'static/guides/beginners-guide/20.png',
'static/guides/beginners-guide/21.png',
'static/guides/faq/double-counting/1.png',
'static/guides/faq/hit-percentage-calculation/1.png',
'static/guides/faq/hit-percentage-calculation/2.png',
'static/guides/faq/hit-percentage-calculation/3.png',
'static/guides/faq/insights/1.png',
'static/guides/faq/insights/2.png',
'static/guides/faq/insights/3.png',
'static/guides/faq/insights/4.png',
'static/guides/faq/insights/5.png',
'static/guides/faq/insights/6.png',
'static/guides/faq/insights/7.png',
'static/guides/faq/insights/8.png',
'static/guides/faq/set-checks-to-default/1.png',
'static/guides/quick-guide/1.png',
'static/guides/quick-guide/2.png',
'static/guides/quick-guide/3.png',
'static/guides/quick-guide/4.png',
'static/guides/quick-guide/5.png',
'static/guides/quick-guide/6.png',
'static/guides/quick-guide/7.png',
'static/guides/quick-guide/8.png',
'static/guides/quick-guide/save-load-scenario.png',
'static/home/1.png',
'static/home/2.png',
'static/home/3.png',
'static/settings/equity-calculator-insights-not-visible.png',
'static/settings/equity-calculator-insights-visible.png',
'static/settings/outcome-analyzer-checkboxes-collapsed-1.png',
'static/settings/outcome-analyzer-checkboxes-collapsed-2.png',
'static/settings/outcome-analyzer-checkboxes-included-1.png',
'static/settings/outcome-analyzer-checkboxes-included-2.png',
'static/settings/outcome-analyzer-hands.png',
'static/settings/outcome-analyzer-insights-not-visible.png',
'static/settings/outcome-analyzer-insights-visible.png',
'static/settings/saved-ranges-1.png',
'static/settings/saved-ranges-2.png',
'static/settings/saved-ranges-3.png',
'static/settings/saved-ranges-4.png',
'static/settings/included-selectors/double-slider-selector.png',
'static/settings/included-selectors/log-double-slider-selector.png',
'static/settings/included-selectors/saved-ranges-selector.png',
'static/settings/included-selectors/single-slider-selector.png',
'static/settings/included-selectors/tier-and-category-selector.png',
'static/settings/tiers/tiers.png',
'static/settings/visual/dont-show-num-combos-in-range.png',
'static/settings/visual/green-grid-squares.png',
'static/settings/visual/multicolored-grid-squares.png',
'static/settings/visual/show-num-combos-in-range.png',
]).then(function () {
console.log('Successfully cached everything.')
}).catch(function (error) {
console.log('Problem caching: ', error);
});
return promise;
}).catch(function () {
console.error('Error with caches.open or cache.addAll');
})
);
});
self.addEventListener('activate', function(event) {
console.log('activate');
event.waitUntil(
caches.keys()
.then(function getOldCachesThatBeginWithPremiumPokerToolsDash (cacheNames) {
console.log(cacheNames);
return cacheNames.filter(function (cacheName) {
return cacheName.startsWith('premium-poker-tools-') && (cacheName !== currCacheName);
});
})
.then(function removeOldCachesThatBeginWithPremiumPokerToolsDash (oldCachesThatBeginWithPremiumPokerToolsDash) {
console.log(oldCachesThatBeginWithPremiumPokerToolsDash)
let removeCachePromises = [];
oldCachesThatBeginWithPremiumPokerToolsDash.forEach(function (oldCacheThatBeginsWithPremiumPokerToolsDash) {
removeCachePromises.push(caches.delete(oldCacheThatBeginsWithPremiumPokerToolsDash));
});
console.log(removeCachePromises);
return Promise.all(removeCachePromises);
})
);
});
self.addEventListener('fetch', function(event) {
console.log('fetch');
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
return response;
}
return fetch(event.request);
}).catch(function () {
console.error('Error trying to match event request to cache.');
})
);
});
The issue comes when I am in a state where I have a service worker installed and active, and I have all of my stuff cached. When I have the dev tools open and have "update on reload" checked in Chrome, if I reload:
The page looks the same, but has the spinner indicating that it is still loading.
In the dev tools, it shows that a new service worker is "waiting to activate".
In the network tab, it shows that the request to http://localhost:8080/ is continually pending.
"Successfully cached everything." is the only thing that gets logged to the console. "activate" doesn't get logged, and neither does "fetch".
But if I press the "x" in Chrome to tell it to stop loading, and then refresh again, it loads perfectly.
I can't seem to figure out what is wrong. / is in the premium-poker-tools-1 cache, so shouldn't the request hit the service worker and return the cached HTML? And even if it doesn't find it there, shouldn't it be sending a request out to the server to get the response? How is it getting hung up?
Edit: I now understand that the service worker is replaced when "Update on reload" is checked even if the service worker hasn't changed.
This answer is moved from the bottom of the question, to make it clearer what the underlying issue is:
I now
understand
that the service worker is replaced when "Update on reload" is checked
even if the service worker hasn't changed.
I'm developing web push notification on my website. I follow the Web Push Notifications of Google and The Service Worker Cookbook of Mozilla.
I have tested on the Google Chrome v50+ and everything is working but I will get the error below on Firefox 44, 45, 46, 52, latest Firefox (version 57.0.4 64 bit) when calling navigator.serviceWorker.register('./push-service-worker.js') function.
TypeError: ServiceWorker script at http://localhost:9600/push-service-worker.js for scope http://localhost:9600/ encountered an error during installation.
This is my code:
Register ServiceWorker in controller.js
navigator.serviceWorker.register('push-service-worker.js')
.then((registration) => {
return registration.pushManager.getSubscription()
.then((subscription) => {
if (subscription) {
return subscription;
}
var subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: buildApplicationServerKey(),
};
return registration.pushManager.subscribe(subscribeOptions);
});
})
.then((subscription) => {
sendSubscriptionToServer(subscription);
})
.catch((err) => {
console.log('Unable to subscribe to push: ', err);
});
push-service-worker.js
'use strict';
self.addEventListener('push', (event) => {
var payload = event.data.json();
var title = payload.title || 'Title';
event.waitUntil(
self.registration.showNotification(title, {
body: payload.body,
icon: './common/images/notification-icon-192x192.png',
image: payload.image || '',
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
var urlToOpen = new URL('/', self.location.origin).href;
event.waitUntil(
clients.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then((windowClients) => {
var matchingClient = null;
for (var i = 0; i < windowClients.length; i++) {
var windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
return clients.openWindow(urlToOpen);
}
})
);
});
Directory structure
./root
---- manifest.json
---- push-service-worker.js
---- src
---- controller.js
Thank for helping!
As wanderview said at here:
FWIW, you should always use a separate profile for each channel (release/beta/dev-edition/nightly). We're working on making it work like that out-of-the-box, but its not ready yet.
This problem is encountered when I use one profile for multiple Firefox version. To fix this issue go to about:support and click Refresh Firefox. If it doesn't work, you can go to about:profiles, click Create new profile, and then Launch profile in new browser.
In my case, this was caused by Firefox not being able to access the serviceworker.js file.
The server I was hosting it on had a permissions check based on cookies, and in this case Firefox was not sending the cookie as I believe it was considered a cross-site script.
On the server, I made the serviceworker.js file accessible publicly, and then Firefox could register the service worker.
It was particularly difficult to notice this, because the Firefox Developer Tools did not show the Forbidden response in the Console, nor did it even show any request for serviceworker.js in the Network tab.
Therefore, presumably the TypeError is in fact a generic error and should be read as 'something went wrong registering the Service Worker'.
I have added service-worker.js and manifest to my website and tested the page using lighthouse which shows my webpage is ready to show "Add to home screen" to user.
But the prompt doesn't shows up twice if user closes the tab without dismissing the prompt first time.Is there any other way to achieve this?.
Here is my manifest
{
"short_name": "TestApp",
"name": "Long Name",
"icons": [
{
"src":"images/favicon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src":"images/favicon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src":"images/favicon-384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src":"images/favicon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/",
"background_color": "#fff",
"theme_color": "#d2d2d2",
"display": "standalone"
}
here is my service worker javascript file
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
// resources to be cached again.
const CACHE_VERSION = 3;
let CURRENT_CACHES = {
offline: 'offline-v' + CACHE_VERSION
};
const OFFLINE_URL = 'offline.html';
function createCacheBustedRequest(url) {
let request = new Request(url, {cache: 'reload'});
// See https://fetch.spec.whatwg.org/#concept-request-mode
// This is not yet supported in Chrome as of M48, so we need to explicitly check to see
// if the cache: 'reload' option had any effect.
if ('cache' in request) {
return request;
}
// If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead.
let bustedUrl = new URL(url, self.location.href);
bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now();
return new Request(bustedUrl);
}
self.addEventListener('install', event => {
event.waitUntil(
// We can't use cache.add() here, since we want OFFLINE_URL to be the cache key, but
// the actual URL we end up requesting might include a cache-busting parameter.
fetch(createCacheBustedRequest(OFFLINE_URL)).then(function(response) {
return caches.open(CURRENT_CACHES.offline).then(function(cache) {
return cache.put(OFFLINE_URL, response);
});
})
);
});
self.addEventListener('activate', event => {
// Delete all caches that aren't named in CURRENT_CACHES.
// While there is only one cache in this example, the same logic will handle the case where
// there are multiple versioned caches.
let expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
return CURRENT_CACHES[key];
});
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (expectedCacheNames.indexOf(cacheName) === -1) {
// If this cache name isn't present in the array of "expected" cache names,
// then delete it.
console.log('Deleting out of date cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', event => {
// We only want to call event.respondWith() if this is a navigation request
// for an HTML page.
// request.mode of 'navigate' is unfortunately not supported in Chrome
// versions older than 49, so we need to include a less precise fallback,
// which checks for a GET request with an Accept: text/html header.
if (event.request.mode === 'navigate' ||
(event.request.method === 'GET' &&
event.request.headers.get('accept').includes('text/html'))) {
console.log('Handling fetch event for', event.request.url);
event.respondWith(
fetch(event.request).catch(error => {
// The catch is only triggered if fetch() throws an exception, which will most likely
// happen due to the server being unreachable.
// If fetch() returns a valid HTTP response with an response code in the 4xx or 5xx
// range, the catch() will NOT be called. If you need custom handling for 4xx or 5xx
// errors, see https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker/fallback-response
console.log('Fetch failed; returning offline page instead.', error);
return caches.match(OFFLINE_URL);
})
);
}
// If our if() condition is false, then this fetch handler won't intercept the request.
// If there are any other fetch handlers registered, they will get a chance to call
// event.respondWith(). If no fetch handlers call event.respondWith(), the request will be
// handled by the browser as if there were no service worker involvement.
});
I could be wrong with this one but if your user has not clicked on the Accept button, I think that is a considered Dismissed.
The only time that your user will be able to see that again is if they cleared their cache and browser settings for that particular site.
Google has also documented:
Source: https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android
You can create better UX, like ask user if they want to install app or by showing install app icon somewhere then ask browser to show the banner.
Right now (Apr 2018) Google updated PWA documentations and it shows developer can decide when to prompt "Add to Home Screen" and they provided a sample code.
Same UX tips described here to ask user for notifications permission.
Here is the sample code they provided:
var deferredPrompt;
window.addEventListener('beforeinstallprompt', function(e) {
console.log('beforeinstallprompt Event fired');
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
return false;
});
btnSave.addEventListener('click', function() {
if(deferredPrompt !== undefined) {
// The user has had a positive interaction with our app and Chrome
// has tried to prompt previously, so let's show the prompt.
deferredPrompt.prompt();
// Follow what the user has done with the prompt.
deferredPrompt.userChoice.then(function(choiceResult) {
console.log(choiceResult.outcome);
if(choiceResult.outcome == 'dismissed') {
console.log('User cancelled home screen install');
}
else {
console.log('User added to home screen');
}
// We no longer need the prompt. Clear it up.
deferredPrompt = null;
});
}
});