I'm using VUE 3 with PWA and I need to call some functions from the registerServiceWorkers.js instead of console logging them, like when an update found or when updated.
this is the service worker:
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details,'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}
any suggestion???
i used this last time:
https://dev.to/drbragg/handling-service-worker-updates-in-your-vue-pwa-1pip
offers you the ability to inform the user for new updates and allows caching them then
Related
I'm writing my first react app with create-react-app but encountered a problem when setting up pwa.
I was trying to show a snackbar on new service-worker registration, but i won't get it working even with the template code.
Below is my service-worker.js under ./src/ (the same as the one in cra template)
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.
And here is my serviceWorkerRegistration.js under ./src/utils. Also nearly the same as the originally shipped one w/ location change and one console.log msg modification (I'm sure ive correctly imported it as the onSuccess callback was functional)
// 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.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
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; see https://github.com/facebook/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. 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 https://cra.link/PWA'
);
});
} 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! Click the pop-up notification to update it!'
);
// 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();
})
.catch((error) => {
console.error(error.message);
});
}
}
I'm registering sw with a useEffect hook:
useEffect(() => {
registerSW({
onSuccess: () => setShowSuccess(true),
onUpdate: sw => {
console.log('onUpdate triggered')
setShowReload(true)
setSW(sw)
},
});
}, []);
The onSuccess callback was working perfectly but the problem was, the onUpdate callback simply wont be triggered. Also the console.log msg in serviceWorkerRegistration itself('New content is available! Click the pop-up notification to update it!') won't appear.
My guess was that some browser apis have changed and the onupdatefound api will no longer work. But I'm not sure if it was because my app call sw to register on every page load, and at that time, the service worker registered is already the new sw.js so that there will not be an update.
I've checked my previous project and found that i was listening on navigator.serviceWorker's controllerchange. I'm not sure which method is correct and how can i implement this feature in react. And im curious about when the onUpdate callback is called and if i was using it correctly.
Thanks in advance!
After some console.log based debugging, i found that this bug was caused by sw registration module loaded after window.onload, causing the template code no longer useful as the registerValidSW function will only be triggered when window.onload target is reached.
I've modified the window.onload part to this to resolve this issue:
const callRegisterValidSW = () => {
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.'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
};
if (document.readyState === "complete") {
callRegisterValidSW();
} else {
window.addEventListener('load', callRegisterValidSW);
}
If someone here sees the maintainer of https://github.com/cra-template/pwa/, we may add some comment around this part in the shipped swRegistration function.
I'm experimenting with service workers, so my scenario is very basic. The problem I'm experiencing is that when I load my page the first time, the service worker doesn't do anything (except installing) and when I reload the page, the service worker is active and intercepts.
Here is my service worker code:
self.addEventListener("install", function (event) {
// self.skipWaiting();
});
self.addEventListener("fetch", function (event) {
const url = event.request.url;
if (url.includes("todos")) {
const response = {
body: { data: "barfoo" },
init: {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/json",
"X-Mock-Response": "yes",
},
},
};
const mockResponse = new Response(JSON.stringify(response.body),
response.init);
event.respondWith(mockResponse);
}
});
If the URL contains todo it sends a mock response back.
The last piece is my index.html:
<html>
<body>
<h1>Test page with service worker</h1>
<button>Fetch</button>
<script>
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register(
"/service-worker.js",
// {
// scope: "/sw-test/",
// }
);
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
document
.querySelector("button")
.addEventListener("click", async (e) => {
const output = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const json = await output.json();
console.log("OUTPUT", json);
});
registerServiceWorker();
</script>
In the html (all is served from http://localhost:8080 btw) is a button which triggers the fetch call on click.
As I said before, the first time I get Service worker installing in the console. Then, when I hit the button I receive the response from jsonplaceholder (self.skipWaiting() didn't help). I can hit the button many times with the same result. Now, when I hit reload I get Service worker active in the console and everything is now working as expected (I get the mock response)
So my question is, what do I have to do to get mock responses initially so the reload is not needed?
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: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});
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
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.
My problem right now is that I have one service worker using old code called service-worker.js I have since decided to completely remove that worker and instead switch to a new one called sw.js
However while i was implementing service-worker.js I had forgot to set my nginx cache to no-cache so now I am stuck with a old service worker with a max life cache using outdated code that hasn't been updated even though I have ran deployments with new code everyday. I have a computer in house that says it has been running on this old service worker for 2 weeks now
Am I just stuck with this old service worker forever now?
I am using SWPrecacheWebpackPlugin with the old service worker and OfflinePlugin for the new
here is the old service worker code
webpack.config
new SWPrecacheWebpackPlugin(
{
filename: 'service-worker.js',
minify: true,
navigateFallback: PUBLIC_PATH + 'index.html',
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/, /(icons)/],
}
),
src/registerServiceWorker.js
export default function register () { // Register the service worker
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = '/service-worker.js';
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);
});
});
}
}
export function unregister () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
Here is the new service worker
webpack.prod.js
new OfflinePlugin({
autoUpdate: true,
ServiceWorker: {
events: true
}
})
index.js
OfflinePluginRuntime.install({
onUpdating: () => {
console.log('SW Event:', 'onUpdating');
},
onUpdateReady: () => {
console.log('SW Event:', 'onUpdateReady');
// Tells to new SW to take control immediately
OfflinePluginRuntime.applyUpdate();
},
onUpdated: () => {
console.log('SW Event:', 'onUpdated');
// Reload the webpage to load into the new version
window.location.reload();
},
onUpdateFailed: () => {
console.log('SW Event:', 'onUpdateFailed');
}
})
In Chrome Dev tools under the Application tab, find the service worker that you want to remove and click on unregister.
I'm using serviceworker-webpack-plugin to create a service worker in my reactjs apps.
I've followed the example to register the service worker in the main thread. I've learnt that Html5 Notification doesn't work on Android chrome, so I used registration.showNotification('Title', { body: 'Body.'}); instead of new Notification('...') to push notifications. But when I tested it on the desktop chrome, it throws this error
registration.showNotification is not a function
Is the registration.showNotification only available on Android chrome but not on the desktop?
public componentDidMount(){
if ('serviceWorker' in navigator &&
(window.location.protocol === 'https:' || window.location.hostname === 'localhost')
) {
const registration = runtime.register();
registerEvents(registration, {
onInstalled: () => {
registration.showNotification('Title', { body: 'Body.'});
}
})
} else {
console.log('serviceWorker not available')
}
}
runtime.register() returns a JavaScript Promise, which is why you are getting a not a function error because Promises don't have a showNotification() method.
Instead, you'd have to chain a .then() callback to it in order to get the actual registration object (or use async/await, which is also cool).
runtime.register().then(registration => {
registration.showNotification(...);
})
Below solution worked for me. Can try.
The main root cause of .showNotification() not firing is service worker. Service worker is not getting registered. So it wont call registration.showNotification() method.
Add service-worker.js file to your project root directory
You can download service-worker.js file from Link
Use below code to register service worker and fire registration.showNotification() method.
const messaging = firebase.messaging();
messaging.onMessage(function (payload) {
console.log("Message received. ", payload);
NotisElem.innerHTML = NotisElem.innerHTML + JSON.stringify(payload);
//foreground notifications
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js', { scope: './' })
.then(function (registration) {
console.log("Service Worker Registered");
setTimeout(() => {
registration.showNotification(payload.data.title, {
body: payload.data.body,
data: payload.data.link
});
registration.update();
}, 100);
})
.catch(function (err) {
console.log("Service Worker Failed to Register", err);
})
}
});