PUBLIC_URL is empty on react on locahost and production? - javascript

PUBLIC_URL is empty on react on localhost and after deploy? how do I set the public URL in react?
console.log(process.env)
production
FAST_REFRESH: true
NODE_ENV: "production"
PUBLIC_URL: ""
WDS_SOCKET_HOST: undefined
WDS_SOCKET_PATH: undefined
WDS_SOCKET_PORT: undefined
dev
Object
FAST_REFRESH: true
NODE_ENV: "development"
PUBLIC_URL: ""
WDS_SOCKET_HOST: undefined
WDS_SOCKET_PATH: undefined
WDS_SOCKET_PORT: undefined
serviceWorker.js
// 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,
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.'
);
});
} 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.'
);
// 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);
});
}
}

Related

create-react-app serviceWorkerRegistration onUpdate callback never seems to be fired

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.

Custom Service Worker in React

I was following a tutorial on creating custom service-worker in React. I got it complete but it is not working as expected. When I visit the page offline, it only renders the default "No Internet" page of Google.
Here is my code:
public/customWorker.js:
console.log("Hello World");
src/sw.js:
workbox.skipWaiting();
workbox.clientsClaim();
self.addEventListener("install", (event) => {
console.log("install");
});
self.addEventListener("activate", (event) => {
console.log("activate");
});
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
src/serviceWorker.js:
// 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.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 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}/customWorker.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(
);
});
} 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."
);
// 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)
.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();
});
}
}
config.js: (in the root of the project)
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
module.exports = function override(config, env) {
config.plugins = config.plugins.map((plugin) => {
if (plugin.constructor.name === "GenerateSW") {
return new WorkboxWebpackPlugin.InjectManifest({
swSrc: "./src/sw.js",
swDest: "service-worker.js",
});
}
return plugin;
});
return config;
};
package.json:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
I ran npm run build and saw the served site in localhost:5000. For the first visit, it prints:
For the second visit:
Any help is greatly appreciated!
Thank You!

ServiceWorker Update loop - Create react app

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.

Service worker with workbox - Uncaught SyntaxError: Unexpected token '<' .. skip waiting

I have react app created with CRA. I want to have custom SW. I am using workbox-build with injectManifest to create my SW.
But I am still stuck with waiting to activate =/ and Uncaught SyntaxError: Unexpected token '<'. Or..
If I use sw-template2.js .. I can skip waiting.. but I still get Uncaught SyntaxError: Unexpected token '<' and white page and I need to manually refresh my page to see content. Any suggestions how to smoothly update my page with that SW please?
My 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.
// To learn more about the benefits of this model, read .
// 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 ('serviceWorker' in navigator) {
// 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}/sw.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
// checkValidServiceWorker(swUrl);
registerValidSW(swUrl);
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
console.log('register ' + swUrl);
navigator.serviceWorker.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker) {
installingWorker.onstatechange = () => {
console.log('state ' + installingWorker.state);
if (installingWorker.state === 'installed') {
console.log('installed ' + navigator.serviceWorker.controller);
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.');
try {
console.log('active start');
let worker = registration.waiting
if (worker) {
console.log('worker start');
worker.postMessage({ type: 'SKIP_WAITING' });
console.log('worker finish');
}
registration.active.postMessage({ type: 'SKIP_WAITING' });
console.log('active finish');
} catch (e) {
}
} 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();
});
}
}
My sw-template.js
if (typeof importScripts === 'function') {
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.3/workbox-sw.js');
/* global workbox */
if (workbox) {
console.log('Workbox is loaded');
self.addEventListener('message', (event) => {
console.log(`The client sent me a message: ${event.data}`);
if (event.data && event.data.type === 'SKIP_WAITING') {
console.log('skipWaiting');
self.skipWaiting();
}
});
workbox.core.clientsClaim();
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
/* custom cache rules*/
workbox.routing.registerRoute(
new workbox.routing.NavigationRoute(
workbox.precaching.createHandlerBoundToURL("/index.html"),
{
denylist: [/^\/_/, /\/[^/?]+\.[^/]+$/],
}
))
workbox.googleAnalytics.initialize()
workbox.routing.registerRoute(
/.*\.(?:png|jpg|jpeg|svg|gif)/,
new workbox.strategies.CacheFirst({
cacheName: 'images',
})
);
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}
sw-template2.js
if (typeof importScripts === 'function') {
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.3/workbox-sw.js');
/* global workbox */
if (workbox) {
console.log('Workbox is loaded');
//self.addEventListener('message', (event) => {
// console.log(`The client sent me a message: ${event.data}`);
// if (event.data && event.data.type === 'SKIP_WAITING') {
// console.log('skipWaiting');
// self.skipWaiting();
// }
//});
self.addEventListener('install', function (event) {
console.log('skipWaiting');
self.skipWaiting();
});
workbox.core.clientsClaim();
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
/* custom cache rules*/
workbox.routing.registerRoute(
new workbox.routing.NavigationRoute(
workbox.precaching.createHandlerBoundToURL("/index.html"),
{
denylist: [/^\/_/, /\/[^/?]+\.[^/]+$/],
}
))
workbox.googleAnalytics.initialize()
workbox.routing.registerRoute(
/.*\.(?:png|jpg|jpeg|svg|gif)/,
new workbox.strategies.CacheFirst({
cacheName: 'images',
})
);
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}

active serviceWorker is not sending message to waiting serviceWorker

What am I trying to do?
I am creating a progressive web application (PWA) and in order to send upgrade the app correctly, I am working on a step where the user is notified and once the user says "upgrade", the serviceWorker calls skipWaiting()
What have I done so far?
I am following up a nice article https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68 to achieve this
In order to remove the complexity, I am only testing sending of messages between serviceWorkers to see how skipWaiting works. I am using create-react-app (v"react": "^16.5.2",) which comes with workbox bundled as plugin.
My current registerServiceWorker.js looks like
// 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.
// To learn more about the benefits of this model, read
// 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) {
// 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) {
// Is not local host. Just register service worker
registerValidSW(swUrl);
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
}
});
window.addEventListener('message', messageEvent => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (messageEvent.data === 'skipWaiting') {
console.log('skipWaiting');
return navigator.serviceWorker.getRegistration(swUrl)
.then(registration => registration.skipWaiting());
}
console.log(`message=${messageEvent.data}`);
});
let refreshingPage;
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('refreshing page now');
if (refreshingPage) return;
refreshingPage = true;
window.location.reload();
});
}
}
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.');
navigator.serviceWorker.controller.postMessage('skipWaiting');
} 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();
});
}
}
As you see, when a new serviceWorker is installed, I am sending a postMessage as
console.log('>> New content is available; please refresh.');
navigator.serviceWorker.controller.postMessage('skipWaiting');
and I am expecting the message to be handled with the function
window.addEventListener('message', messageEvent => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (messageEvent.data === 'skipWaiting') {
console.log('skipWaiting');
return navigator.serviceWorker.getRegistration(swUrl)
.then(registration => registration.skipWaiting());
}
console.log(`message=${messageEvent.data}`);
});
Then, I deploy my changes so that this serviceWorker is ready.
Then, I make changes to my application (index.html) and now when I deploy, I see multiple messages being logged, but none with skipWaiting
message=
registerServiceWorker.js:53 message=!_{"h":"I0_1548194465894"}
registerServiceWorker.js:53 message=!_{"s":"/I0_1548194465894::_g_rpcReady","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":1,"a":[null],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[1,[null]],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[2,[null]],"g":false}
registerServiceWorker.js:53 message=!_{"s":"/I0_1548194465894::_g_restyleMe","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":2,"a":[{"setHideOnLeave":false}],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[4,[null]],"g":false}
registerServiceWorker.js:53 message=!_{"s":"/I0_1548194465894::authEvent","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":3,"a":[{"type":"authEvent","authEvent":{"type":"unknown","eventId":null,"urlResponse":null,"sessionId":null,"postBody":null,"tenantId":null,"error":{"code":"auth/no-auth-event","message":"An internal error has occurred."}}}],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[3,[true]],"g":false}
What am I doing wrong here?
There a few things wrong
navigator.serviceWorker.controller.postMessage('skipWaiting');
This is sending a message from window to worker, but you're waiting for the message on the window
controller -> is not the worker instance. postMessage() would send the message to that worker.
You don't want to post to that worker though - it's your current active worker, while you want to notify the newly installed worker that haven't taken over yet
Even if you manage to run the code in the message event handler it won't do
if (messageEvent.data === 'skipWaiting') {
console.log('skipWaiting');
return navigator.serviceWorker.getRegistration(swUrl)
.then(registration => registration.skipWaiting());
}
There's no registration.skipWaiting() method. We call skipWaiting inside the service-worker.js code
In your case you would add an event listener for message inside the worker code and intercept the "Skip Waiting" message there
worker code
self.addEventListener('message', (event) => {
if (event.data == 'skipWaiting') self.skipWaiting();
});
window code (changes only)
Send a message to the now installed worker to skip waiting and take over
console.log('>> New content is available; please refresh.');
installingWorker.postMessage('skipWaiting');

Categories

Resources