Reputation: 58
I'm trying to implement a popup for the user that tells them that an update is available.
I'm using Create React App and it's default configuration. I'm dispatching an action when it founds an update like so:
index.js
serviceWorker.register({
onSuccess: () => store.dispatch({ type: SW_UPDATE_FINISHED }),
onUpdate: reg => store.dispatch({ type: SW_UPDATE, payload: reg })
});
and I have a little component that should take care of showing the message and triggering the update:
const SwUpdate = () => {
const swUpdate = useSelector(state => state.app.swUpdate);
const swReg = useSelector(state => state.app.swReg);
const dispatch = useDispatch();
const updateSw = () => {
const swWaiting = swReg && swReg.waiting;
if (swWaiting) {
swWaiting.postMessage({ type: 'SKIP_WAITING' });
dispatch({ type: SW_UPDATE_FINISHED });
window.location.reload();
} else {
dispatch({ type: SW_UPDATE_FINISHED });
}
}
return (
<Snackbar
open={swUpdate}
color="primary"
message="New version available! 🎊"
action={
<Button color="primary" size="small" onClick={updateSw}>
UPDATE
</Button>
}
/>
)
}
export default SwUpdate;
It does what I would think it should do, reloads the page... but...
Again... you have an update! and everytime a do it manually from the inspector is the same story, I get a new SW and I'm not getting why that happens
I cannot seem to find an example of this happening to another person and I'm worried is something that I'm doing wrong, or something wrong with the CRA service worker, I only added the callbacks to the register's config argument and manage it on the specific component.
Edited: Add the service-worker code
I have the serviceWorker.js
AS IS from Create react App:
// This optional code is used to register a service worker.
// register() is not called by default.
// 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 subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
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;
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit <LINK>'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See '
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} 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.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.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, config);
}
})
.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();
});
}
}
I know that they use Workbox for the configuration, I'm testing all of this by running npm run build
and serve -s build
and reloading to try it out.
ALSO, here's the service-worker file generated by Workbox's configuration of Create react app when building the app:
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
*/
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"/precache-manifest.a4724df64b745797c25a9173550ba2d3.js"
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
workbox.core.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
*/
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});
Upvotes: 1
Views: 2120
Reputation: 11
I faced the exact same issue, whenever updating the pop up would show to update the page, and new service worker would be in waiting state. I believe the issue was coming from the checkbox Update on Reload
(on Application tab) being ticked on. Once I turned it off, everything seemed to work as expected.
Upvotes: 1
Reputation: 58
Apparently, having another service-worker required by Firebase messaging for push notifications were triggering an update everytime.
I honestly didn't mentioned it in the original question because I thought it didn't have anything to do with my problem.
I ended up setting up FCM to use my service worker instead of having it own, and it all started to work properly :)
I can't say much more since I'm not 100% sure WHAT was the problem really, probably the FCM serviceworker wasn't updating itself and made the other one try to update itself everytime? serviceworkers are weird
Upvotes: 1