Add Service Worker to Create React App Project - reactjs

I am installing a notification system called Subscribers with a project built with create react app. They ask the developer to download their service worker and include it in the root directory of the project. I have never had to install / use a service worker before, which is most likely the root of my misunderstanding.
How do you add a service worker into the root directory of a React project? The instructions say the service worker should appear in the root directory as https://yoursite.com/firebase-messaging-sw.js. In an attempt to register that URL, I included a service worker under src/index.js:
import Environment from './Environment'
export default function LocalServiceWorkerRegister() {
const swPath = `${Environment.getSelfDomain()}/firebase-messaging-sw.js`;
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register(swPath).then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
}
In production, I receive a 404 error. I have tried placing the firebase-messaging-sw.js file in the root directory, and under the src folder. Same error each time.
Here are the instructions from Subscribers:
https://subscribers.freshdesk.com/support/solutions/articles/35000013054-diy-installation-instructions

The public folder is the provided 'escape hatch' for adding assets from outside of the module system.
From the docs:
If you put a file into the public folder, it will not be processed by Webpack. Instead it will be copied into the build folder untouched. To reference assets in the public folder, you need to use a special variable called PUBLIC_URL
So, once copied to the public folder, you would reference the file like this:
import Environment from './Environment'
export default function LocalServiceWorkerRegister() {
const swPath = `${process.env.PUBLIC_URL}/firebase-messaging-sw.js`;
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register(swPath).then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
}

Related

Aws Amplify (React PWA) - Clear Site Cache Data after changes

I have a React PWA application on Aws Amplify, everytime there are repository updates, user is constrain to make this actions: 1. open chrome console, 2. Go to Application Tab, 3. Go to Storage 4. Clear Site Data 5. Refresh Application.
I noticed that every update main.js go in 404.
Is there a way for clear cache after new changes?
public/serviceWorker.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('serviceWorker.js').then(function (registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function (err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
Partially solved.
Cache issue is solved, alert doesn't appear.
Removed js code from index.html
Moved serviceWorker from public to src
Added follow code to App.js;
import * as serviceWorker from "./serviceWorker";
serviceWorker.register({
onUpdate: registration => {
alert('New version available!');
if (registration && registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
window.location.reload();
}
});

CSP headers are missing in JS files (while CSS not) when served by service worker

I am using a brand new app generated by create-react-app 3.4.1. It uses the default service worker file:
// 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}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: 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.'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: 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: string, config?: 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 turned on service worker by changing the code in index.ts to
serviceWorker.register();
I hosted the static files generated by yarn build through https by an Express.js server with strict Content Security Policy (CSP) turned on by helmet.
helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: [
/* Content Security Policy Level 3 */
"'strict-dynamic'",
`'nonce-${cspNonce}'`,
/* Content Security Policy Level 2 (backward compatible) */
"'self'",
// Workbox
'https://storage.googleapis.com',
// ...
],
styleSrc: [
"'self'",
],
// ...
},
},
})
When I first time opening the page, the browser fetch files from server. Both JS and CSS have CSP headers. The page shows well.
When I second time opening the page, the files are loaded from service worker. Many got blocked by CSP, as my console shows:
When I further check, CSS files served by service worker still have CSP headers (and nonce inside also changed to new value, create-react-app did it for us?), which load well.
However, the CSP headers on JS files are missing, which got blocked.
Any guide will be helpful. Thanks!
UPDATE
One thing I notice in Chrome, it shows
CAUTION: provisional headers are shown
and I found more info at
"CAUTION: provisional headers are shown" in Chrome debugger
Another thing I found, the page won't load on second call on Chrome and Safari after service worker (create-react-app uses Workbox internally) registered.
For Firefox, although CSP headers are not shown neither in JS and CSS files when read from cache, Firefox still can show the page.
It is likely that the first time you load the page the nonce in your CSP and your script tags are in sync. On the second load they are no longer present or in sync in your script tags. Check the difference in nonce values in the CSP header and inline script tags.
CSP applies to pages being rendered in the browser (content-type: "text/html"), it doesn't have any effect when set on the other resources loaded. Missing CSP header on js files doesn't have any effect. Your CSS files are included because you include "style-src 'self'", you should add this to script-src as well. If it is not sufficient you could add localhost:5000 in development.
As noticed Halvor Sakshaug above, you do not need to serve JS/CSS with CSP headers, CSP work only for page/code having document property.
As seen from you Chrome console warnings, there is at least 2 issues:
an inline scripts blocked (you do use < script>...< /script> or < tag unClick='...'> somewhere). So you have to add 'unsafe-inline' to script-src (or add nonce='server_generated_value' attribute to < script>...< /script>), BUT:
'strict-dynamic' cancels host-based allowlisting (incliding 'self') in CSP3-browsers, so your https://localhost (and other hosts) will be disabled. Also 'strict-dynamic' cancels 'unsafe-inline' ('nonce-value' and 'hash-value' cancel it too). Probably you do not sign inline scripts with nonce='server_generated_nonce' attribute. Or you do use scripts calls incompatible with 'strict-dynamic' (parser-inserted scripts, inline event handlers etc)
You have to revise Content Security Policy rules, they are inconsistent.

How to cache React application in service worker

I'm trying to create a React PWA from scratch. So far my project outputs the minified files to a dist/js folder.
In my service worker file I'm using Workbox to precache the app. This is my setting so far:
importScripts("./node_modules/workbox-sw/build/workbox-sw.js");
const staticAssets = [
"./",
"./images/favicon.png",
]
workbox.precaching.precacheAndRoute(staticAssets);
Currently if I enable offline from dev tools > Service Workers, it throws these errors and the app fails to load:
3localhost/:18 GET http://localhost:8080/js/app.min.js net::ERR_INTERNET_DISCONNECTED
localhost/:1 GET http://localhost:8080/manifest.json net::ERR_INTERNET_DISCONNECTED
3:8080/manifest.json:1 GET http://localhost:8080/manifest.json net::ERR_INTERNET_DISCONNECTED
logger.mjs:44 workbox Precaching 0 files. 2 files are already cached.
5:8080/manifest.json:1 GET http://localhost:8080/manifest.json net::ERR_INTERNET_DISCONNECTED
How can I fix this?
this means your resources are not getting cached properly,
you need to add them to cache before accessing,
workbox by default do it for you.
it shows 2 files cached, as they present in your array, expected result
same do it for all remaining too.
const staticAssets = [
"./",
"./images/favicon.png",
"./js/app.min.js",
"./manifest.json",
{ url: '/index.html', revision: '383676' }
]
you can try to add eventlistener,
self.addEventListener('install', event => {
console.log('Attempting to install service worker and cache static assets');
event.waitUntil(
caches.open("staticCacheName")
.then(cache => {
return cache.addAll(staticAssets);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});

Not seeing changes made to static files when using service worker

I have registered a service worker for a reactjs app.
I am using the cache api with it. When I make changes to my service worker script the activate event is called and I am able to update the the service worker
The challenge is with making changes to the source files. I am unable to see these changes even after updating the service worker, the only time I see changes is when I manually register a new service worker all together.
this is a snippet of the activate event listener
self.addEventListener('activate', event => {
const expectedCacheNames = Object.values(CURRENT_CACHES);
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if(!expectedCacheNames.includes(cacheName)) {
console.log('Deleting out of date cache: ', cacheName);
return caches.delete(cacheName);
}
})
)
})
)
});
Is this problem specific to react?
code structured as follows
/src
/components
index.js //this is where I register service worker after calling ReactDOM
/public
sw.js
//other assests ...

Register service worker in angular.js application

I'm creating an app using ionic and angular.js and I'm having difficulties registering a service worker which I'm intending to use to add the new app install banner feature. I'm adding the below code on my app.js file as instructed, but I'm note getting any signals of the registration happening nor any error.
This is the code I'm adding to my app.js:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js').then(function(registration) {
//Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
//registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
}
Make sure you either load the page at localhost or you have to use https
You’ll also need to serve your code via HTTPS — Service Workers are restricted to running across HTTPS for security reasons. GitHub is therefore a good place to host experiments, as it supports HTTPS.
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker_API/Using_Service_Workers
Check if your Browser supports this feature.
if ('serviceWorker' in navigator) {
[...]
} else {
console.log("this browser does NOT support service worker");
}
This might help: http://caniuse.com/#feat=serviceworkers
If you want to see the current state of your serviceworker you could do something like this:
navigator.serviceWorker.register('/serviceworker.js', {scope: '/'})
.then(function (registration) {
var serviceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
} else if (registration.waiting) {
serviceWorker = registration.waiting;
} else if (registration.active) {
serviceWorker = registration.active;
}
if (serviceWorker) {
console.log("ServiceWorker phase:", serviceWorker.state);
serviceWorker.addEventListener('statechange', function (e) {
console.log("ServiceWorker phase:", e.target.state);
});
}
}).catch(function (err) {
console.log('ServiceWorker registration failed: ', err);
});
If you're not seeing anything logged, then the most likely cause is that you're running in a browser that doesn't support service workers. In other words, the if ('serviceWorker' in navigator) check fails. You can confirm this by adding in a logging statement in an else clause associated with that if.
Which browser are you testing with? Service workers are coming to more browsers in the future, but as of right now, they're only enabled by default in the current versions of Chrome on desktop and Android platforms.

Resources