I'm building my first progressive web application. I'm trying to have delete cache button, is there a way to delete all files excluding certain files & also the ones caches by the service work also.
I seen caches.delete() been used but can i excluding files using that?
delete cache:
self.caches.keys().then(keys => { keys.forEach(key => console.log(key)) })
self.caches.delete('demo')
service worker:
var cacheFiles = [
'/',
'/index.html',
'/style.css'
];
self.addEventListener('install', function(e) {
e.waitUntil(
caches.open("demo").then(function(cache) {
return cache.addAll(cacheFiles);
})
);
});
I gather from your question that you want to delete only certain files from a cache.
I think that's possible, using something like this:
function deleteFileFromCache(fileName, cacheName) {
caches.open(cacheName).then(function(cache) {
cache.delete(fileName).then(function(response) {
console.log('cache.delete response:', response);
}
}
At least the MDN docs imply this will work.
Related
I'm trying to get a basic service worker-based updating capability going for my web app.
(You'll see in the code below that I clear the cache by specific name - 4.7 - and by dynamic name. This is because it seems to me that the code I'm using clears the NEW cache name, not the old one. Maybe a separate issue. Right now I'm clearing both.)
If I change the cacheName and the annotateAccount.js file (this running on localServer) I can see that the service worker does its job: downloads a new version from the server (I see a status of "200 OK" in the console vs. "200 OK (from service worker)" when running a page refresh without an update to the service worker file.
Even though the code downloads a new version of the JS, my actual app (running in Chrome) pulls from an old cached version of the file.
If I "empty cache and hard reload" I get the new version. If I immediately do a regular refresh I get the old (browser cached) version of the file.
I've read enough to know there is browser caching on top of service worker caching, but what's the F-ing point of using a service worker if you can't overcome the browser's caching? I'm using XAMMP/Apache to run my localhost dev environment. I haven't tried this in a staging or production environment on my actual servers. Since I seem to be fighting Chrome's local caching features I'm not sure that matters.
I'd previously just put "?v=xxx" after filenames throughout my app, and changed the parameter when I released updates, in an attempt to give users the latest code. This doesn't work well.
What am I missing? Any direction/help appreciated.
Here's my service worker JS file
var urlsToCache = [
'annotateAccount.html',
'annotateAccount.js',
'web_background.js'
];
const cacheName = '4.8';
self.addEventListener('install', event => {
console.log('Install event...', urlsToCache);
event.waitUntil(
caches.open(cacheName)
.then(function(cache) {
console.log('Opened cache', cacheName);
return cache.addAll(urlsToCache);
})
);
});
var url;
self.addEventListener('fetch', (event) => {
//Cache falling back to network
url = event.request.url.split(".").pop(); // check php
if (url !== "php"){
event.respondWith(
caches.match(event.request)
.then((response) => {
return response || fetch(event.request);
})
);
} else { // END check for PHP - skip trying cache/SW.
console.log('Trying a php file - always let code run it...');
}
});
self.addEventListener('message', function (event) {
console.log('ServiceWorker cache version: ', cacheName, event);
console.log('Received msg1: ', event.data.action);
if (event.data.action === 'skipWaiting') {
console.log('ccClearing cache: ', cacheName);
caches.delete(cacheName); // actually removes cached versions
caches.delete("4.7"); // delete NAMED cache...
self.skipWaiting();
} else {
console.log('Do not clear cache...');
}
});
I am currently working on a proof-of-concept for a project but I just can't get my head around
documentation
and implementation :)
The case is as follows:
I have my main app (React) that has a list of links. All of them link to a specific page.
These links open up in an iframe.
That's all basically.
So my app runs on "app.domain.com" and the forms urls are like "pages.domain.com/pages/pageA.html" etc.
What I need to do in this poc is to make these pages offline available, including(!) the assets for this pages (css/js/img)
I already created a simple service worker.
const CACHE_NAME = "poc-forms";
self.addEventListener("install", (event) => {
console.log("sw installing…");
});
self.addEventListener("activate", (event) => {
console.log("sw now ready to handle fetches");
event.waitUntil(caches.open(CACHE_NAME).then(() => self.clients.claim()));
});
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (url.pathname.includes("forms")) {
event.respondWith(
(async function () {
var cache = await caches.open(CACHE_NAME);
var cachedFiles = await cache.match(event.request);
if (cachedFiles) {
return cachedFiles;
} else {
try {
var response = await fetch(event.request);
await cache.put(event.request, response.clone());
return response;
} catch (e) {
console.log(" something when wrong!");
}
}
})()
);
}
});
It fetches the request and checks if it's already in the cache or not.
If it's not, cache it.
This works.
But where I'm stuck:
how can I also store the css and js that are needed for the pages as well? Do I need find a way to get all links and loop over them, fetch them and store them?
I heard about Google Workbox, also went through the documentation but it's just not clear to me, like how to transform my current SW into something that works with workbox, with the whole registerRoute-thing on a fetch...
the service worker will only capture the fetches when the page is refreshed. The clients.claim() should fix this, but it doesn't actually...
If someone out there could help me out with this, much appreciated!
thanks,
Mario
The Service Worker APIs do not allow for a service worker registered on app.domain.com to control either navigation requests or subresource requests associated with an <iframe> that is hosted on pages.domain.com. That's not something that Workbox can help with—it's just not possible.
From the perspective of the service worker, an <iframe> with a different origin than the host page is equivalent to a new window or tab with a different origin. The service worker can't "see" or take control of it.
Also, this might help others, small variant of what I want to accomplish:
Why isn't my offline cached subdomain page not loading when called from other subdomain?
Ok, we ended up using a different approach, because the app we are loading inside the iframe exposed an api apparently, where we can hook into.
To cache the entire page, with all the assets, this was the code that worked for me:
importScripts(
"https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js"
);
const { skipWaiting, clientsClaim } = workbox.core;
const { registerRoute } = workbox.routing;
const { StaleWhileRevalidate } = workbox.strategies;
skipWaiting();
clientsClaim();
registerRoute(
({ request }) => {
console.log(" request ", request.destination);
return (
request.destination === "iframe" ||
request.destination === "document" ||
request.destination === "image" ||
request.destination === "script" ||
request.destination === "style" ||
request.destination === "font"
);
},
new StaleWhileRevalidate()
);
I have built PWA enabled SPA by using Vue, js, node technology. Problem is that service worker deletes my stored cache for viewing app offline whenever i switch off my PC, otherwise it works. Second problem is that it refuses to fetch data that includes google fonts, unless I enable CORS in the browser. Since PWA is based on the browser and users will not have CORS add-on installed in the browser, is there some way to enable CORS on the (Windows) server? Thanks in advance, here is my service worker code.
// service worker file. Every time when you change this file rename staticCache const in order to changes to be visible when user closes tab and reopens it.
const staticCache = 'site-static-1'; // static cache for main site files, app shell (all static files, html, css, starting images, logo etc). If you change code always rename this to new number
const assets = [ // this is array of app shell API requests for assets. Those are keys and asset values (images etc) will be values of key/value pair in array
'/',
'/index.html',
'/app.c328ef1a.js',
'/app.c328ef1a.css',
'/manifest.webmanifest',
'/photo-login.04703ebf.jpg',
'/icon_area.9bfa0c9a.png',
'/icon_144x144.c75152b5.png',
'/img/avatar_default.png',
'/img/icon_pwa.png',
'https://fonts.googleapis.com/css?family=Raleway:300,400,600,700',
'https://code.highcharts.com/stock/highstock.js',
'https://code.highcharts.com/stock/modules/data.js',
'https://code.highcharts.com/stock/modules/exporting.js',
'https://cdn.pubnub.com/sdk/javascript/pubnub.4.21.7.min.js',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwIYqWqhPAMif.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptug8zYS_SKggPNyCMIT5lu.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptug8zYS_SKggPNyC0ITw.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwPIsWqhPAMif.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwPIsWqZPAA.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwJYtWqhPAMif.woff2',
'https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwJYtWqZPAA.woff2'
]; // we hve to fetch separately fonts from links inside fonts.googleapis.com
// In Chrome Web Tools go to Application>Cache storage and click /css?family=raleway, links are inside value of that key
// installing service worker event
self.addEventListener('install', evt => {
console.log('Service worker has been installed');
// programmatically skip awaiting for new changed sw file to become active, because sometimes closing Chrome and tabs is not enough
// if we change sw.js and want to make sure change is visible ie cache is refreshed, we need to change version number of staticCache constant.
// NOTE: If we save this file by adding asset to be fetched (image for example) it will be visible in a new cache upon clicking browser reload.
// ..but if we delete it from the list of items to be fetched, IT WILL REMAIN in the cache until we change staticCache version number, save and reload browser page.
// So it is best practice to always change version number in staticCache whenever you make and save changes.
self.skipWaiting(); // it will be triggered only if there is a new sw version that awaits to be executed
evt.waitUntil( // installing awaits until this is executed first, otherwise it could stop it
caches.open(staticCache).then(cache => { // it opent cache, if there isn't it will create one
cache.addAll(assets); // add into cache all assets from the assets array []
})
);
});
// activating service worker event
self.addEventListener('activate', evt => {
console.log('Service worker has been activated');
evt.waitUntil(
caches.keys().then(keys => { // get array with keys (of key/value pair) from different cache versions in Chrome Dev Tools>Application>Cache Storage
// go thru all caches keys array and delete all values except newest cache, named in staticCache const. That way only the last cache is used by an app
return Promise.all(keys
.filter(key => key !== staticCache)
.map(key => caches.delete(key))
)
})
);
});
// fetch event
self.addEventListener('fetch', evt => {
console.log('SW is fetching data');
evt.respondWith( // check if requested data is stored in the cache.
caches.match(evt.request).then(cacheRes => {
return cacheRes || fetch(evt.request) // if item is in cache use it, if isn't go to the server and fetch it
})
)
});
Seems like the issue is with the activate event handler in the service worker. This is mainly using for removing the old cache from the browser. Try replacing the event listener with the following code
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== staticCache) {
return caches.delete(cache); //Deleting the old cache (cache v1)
}
})
);
})
.then(function () {
console.info("Old caches are cleared!");
return self.clients.claim();
})
);
});
I have a reactJs app using create-react-app. The app uses service-worker and other PWA features but somehow I am seeing that despite updating the website or deploying a new build, chrome always picks index.html from service worker and does not make a network call at all.
I think caching the index.html using the service worker is the issue but have not been able to exclude it from getting cached, I did check a few questions on SO and issues on github but could not get a fix for this.
I am using the default service-worker registration
registerServiceWorker.js
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (/*process.env.NODE_ENV === 'production' &&*/ 'serviceWorker' in navigator) {
// alert('found' + process.env.NODE_ENV);
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
} else {
//alert('not found' + process.env.NODE_ENV);
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('Content-Type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
Please help.
After trying multiple options, the one that worked best was not I was really willing to implement but could not find anything better.
From the service-worker.js file remove below lines
workbox.routing.registerNavigationRoute("/index.html", {
blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/],
});
This will not cache index.html and try to pull it every time from the server, though this does not sound best but does the trick.
If you have ejected from create-react-app you can go into /config/webpack.config.js and change the default arguments for GenerateSW() to the following:
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/, /index.html/],
importWorkboxFrom: "cdn",
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp("^/_"),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp("/[^/]+\\.[^/]+$")
]
This way you don't have to manually change your serviceworker.js after every building.
For people using InjectManifest instead of GenerateSW
As you probably found out, the blacklist and navigateFallbackBlacklist options are not allowed with InjectManifest.
My solution was to make a index-shell.html which can be cached and keep the original index.html out of the cache.
new InjectManifest({
include: [
/index-shell\.html$/,
],
exclude: [
/index\.html$/,
],
modifyURLPrefix: {'/public/': '/'}, // solved my routing issues with publicPath and what not...
...
})
I would like to benefits from offline capabilities, so I m making sure to cache on install the page it self and other assets like css and js
self.addEventListener('install', function (event) {
event.waitUntil(caches.open(cacheName).then(cache => cache.addAll([
"/",
self.location.href
])));
self.skipWaiting();
});
as install will be run only once for the worker, how can I make sure that those assets get refreshed on every run or at least from time to time?
You can achieve this also by defining a new cache name each time you deploy a new version of your website:
//change cache name with each deploy
var staticCacheName = 'NewWithEachDeploy';
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(staticCacheName).then(function (cache) {
return cache.addAll([
'index.html',
'...'
]);
})
);
});
Then on activate you delete all the old caches.
self.addEventListener('activate', function (event) {
event.waitUntil(caches.keys().then(function (keyList) {
return Promise.all(keyList.map(function (cacheKey) {
//delete all caches which do not equal with the current cache name (staticCacheName)
if (cacheKey !== staticCacheName) {
console.log('[ServiceWorker] Removing old cache', cacheKey);
return caches.delete(key);
}
}));
}));
});
This way you make sure all resources are updated at once. Otherwise you can get in trouble. For example your cached html is updated to the newest version but the JS file is still old and this constellation probably does not play well together.
While implementing cache expiration using vanilla JS can be bit challenging, you can try using Workbox by Google. It has this concept of cache expiration.
A super basic example:
const cacheExpirationPlugin = new workbox.cacheExpiration.CacheExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 24 * 60 * 60
});
You could read more about it here.