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...
...
})
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()
);
What I'd like to achieve is the ability to push a notification to the user when an update is available to my React webpage, and I am trying to do so via serviceWorkers.
The challenge I am facing is to get the installation property of the serviceWorkerRegistration interface to not return null. I just want to trigger
console.log('New content is available; please refresh.')
I am quite new to working with serviceWorkers in React. Does anyone have an idea on how to go about testing the update functionality of serviceWorkers in a development environment?
console.log('I am in registerValidSW!')
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
console.log(registration)
registration.onupdatefound = () => {
const installingWorker = registration.installing //This returns null
if (installingWorker) {
console.log('installingServiceWorker!!!')
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)
})
#krish according to new documentation-> The service worker is only enabled in the production environment, e.g. the output of npm run build. It's recommended that you do not enable an offline-first service worker in a development environment, as it can lead to frustration when previously cached assets are used and do not include the latest changes you've made locally. refer this link :- https://create-react-app.dev/docs/making-a-progressive-web-app/
I'm developing a reactjs based application. I also made service-worker settings on it. After add to home screen , application never checks the server for new updates.
I also tried:
window.location.reload(true);
But it doesn't update new version.
I'm using Apache server to serve build folder and for update I'm getting a new build of my project and serve that on Apache server.
I finally resolved my problem after two days. The problem was in service-worker file. I had to add event listener if page reloaded and server files had changes so it will update the files.
So I added this section to serviceWorker.js in register function:
window.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
Just don't forget. This listener call when page is reload. So I make API service to check there is new version or not. if there is new version , It have to reload the page to get new files.
this question was so helpful: How to clear cache of service worker?
Update (December.1.2019):
I found better way to update new PWA. Actually that way (above) not work on iOS 13. So I decide check update by API. PWA Send current version to API and if there is new version released , in PWA we should delete all caches:
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
And after that reload application:
window.location.href = "./";
After reload because there is no cache to load pages on offline mode, so PWA will check server and get new version.
this work for me:
src/index.tsx
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
serviceWorkerRegistration.register({
onUpdate: (e) => {
const { waiting: { postMessage = null } = {} as any, update } = e || {};
if (postMessage) {
postMessage({ type: 'SKIP_WAITING' });
}
update().then(() => {
window.location.reload();
});
},
});
Our app is a Polymer 2 single page app, where we have some custom build steps to generate versioned files of the resources (gulp-rev-all). All is working well, but we need to have implement a safe refresh of the application. What we are doing is that we keep latest installed git commit in a file that is served together with the application, and we pull for this file in intervals and alerts the user that there is a new version available and ask them to click on a button to refresh the application.
The problem is that we are using service worker with pre-cache as the default Polymer build provides. This means when we do location.reload() we actually get the content (index.html) from the service worker, and not from the server.
So the question is: how can we enforce that the service worker would be invalidated and force a new refresh of service-worker.js and index.html?
Here the ready code, all described how to handle service worker ;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(function(reg) {
// updatefound is fired if sw.js changes.
reg.onupdatefound = function() {
// The updatefound event implies that reg.installing is set; see
// https://w3c.github.io/ServiceWorker/#service-worker-registration-updatefound-event
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case '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 the page's interface.
console.log('New or updated content is available.');
// Need to hard refresh when new content is available
location.reload(true);
} 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 now available offline!');
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
}