Firebase Messaging service worker on chrome - javascript

I am trying to implement FCM chrome client on Eclipse IDE.
Using Javascript, I am trying to register the default service worker : firebase-messaging-sw.js but the path for this js file cannot be resolved by project.
The SDK is searching for this file at hosting level: /firebase-messaging-sw.js i.e. https://localhost:8080/firebase-messaging-sw.js but not at https://localhost:8080/myapp/firebase-messaging-sw.js
Therefore, I get Error :
Firebase Service worker not found while using GWT (404 Error)
How can I resolve this issue ?

Service workers by default should reside on the root of the application. To register a service worker st some other location than the default location, you can use the following code.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw-test/sw.js', {scope: '/sw-test/'})
.then(function(reg) {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
From MDN,
The outer block performs a feature detection test to make sure service workers are supported before trying to register one.
Next, we use the ServiceWorkerContainer.register() function to register the service worker for this site, which is just a JavaScript
file residing inside our app (note this is the file's URL relative to
the origin, not the JS file that references it.)
The scope parameter is optional, and can be used to specify the subset of your content that you want the service worker to control. In
this case, we have specified '/sw-test/', which means all content
under the app's origin. If you leave it out, it will default to this
value anyway, but we specified it here for illustration purposes.
The .then() promise function is used to chain a success case onto our promise structure. When the promise resolves successfully, the
code inside it executes.
Finally, we chain a .catch() function onto the end that will run if the promise is rejected.
This registers a service worker, which runs in a worker context, and
therefore has no DOM access. You then run code in the service worker
outside of your normal pages to control their loading.

Related

Using JS serviceWorker as identity proxy - trying to detect `access_token` unregister

I am evaluating using a JS ServiceWorkers as an identity proxy, injecting the access_token on fetch() calls.
const addAuthHeader = function (event) {
destURL = new URL(event.request.url);
if (whitelistedOrigins.includes(destURL.origin) && whitelistedPathRegex.test(destURL.pathname)) {
const modifiedHeaders = new Headers(event.request.headers);
if (token) {
modifiedHeaders.append('Authorization', token) //< Injection
}
const authReq = new Request(event.request, {headers: modifiedHeaders, mode: 'cors' });
event.respondWith((async () => fetch(authReq))());
}
}
// Intercept all fetch requests and add the auth header
self.addEventListener('fetch', addAuthHeader);
The token is stored in a closure variable within the serviceWorker class. Click here for more information about this approach.
One problem I am running into is that when the serviceWorker is updated, the token variable is being overwritten and the access_token is lost.
Is there a way to detect that the serviceWorker has been updated? Or, to protect the token variable? Is there a design pattern/standard you can point me towards related to using serviceWorker as an identity proxy as I have done?
I would advise against an approach that requires a service worker to be in control of a given page for anything security/identity related.
First off, users who visit your page for the first time will not have a service worker installed yet, so it won't be in control of the current page. Additionally, users who are familiar with the browser's developer tools can unregister a service worker at any time, and/or use shift-reload to visit your page without the active service worker in control. So you really can't rely on the service worker always being there.
Second, a service worker's global state is short-lived, and you can't rely on variables being present across multiple event invocations. There are more details about that in "When does code in a service worker outside of an event handler run?".
Generally speaking, you should consider the behavior inside of a service worker as progressively enhancing the core functionality of your web app, and not move any required functionality to the service worker.

Serviceworker registration in Chrome sometimes hangs

Sometimes registration of the service worker (SW) hangs. 
Since our PWA relies on the SW, and won't start without it, it looks like our App hung on startup. 
It seems I need to code an escape hatch. 
I seek suggestions.
The fact that the registration hangs was established by inserting 90 second timeout, as you see below.
const serviceWorker = navigator.serviceWorker;
if( serviceWorker && typeof serviceWorker.register === 'function' ) {
swRegisterTimeout = setTimeout(swRegTimedOut,90000);
serviceWorker.register('serviceworker.js')
.then(onServiceworkerRegistered)
.catch(function(err){
clearTimeout(swRegisterTimeout);
message("Service Worker Register Failure");
})
} else {
if ( serviceWorker ) {
message("'serviceWorker.register' is not a function")
}else{
message("'navigator.serviceWorker' does not exist")
}
}
});
function swRegTimedOut(){
message("ServiceWorker unable to register in 90 seconds")
}
function onServiceworkerRegistered(){
var serviceWorker = navigator.serviceWorker,
postBox = serviceWorker.controller;
clearTimeout(swRegisterTimeout);
message("Service Worker Registered");
serviceWorker.ready.then(function(reg){
if(reg.active){
// More stuff
}
})
}
This happens only when our internet is operable, but barely. 
Then I get the hang - with the timeout message ninety seconds later - every time, and the App does not launch.
If there is no internet, no hang; everything starts instantly. 
In fact, I can break the hang by turning off Wifi on the R&D machine. 
Boom! 
Everything loads immediately.
I speculate that somewhere in the registration code, I/O is being attempted. 
If said I/O does not fail or succeed, the registration does not come back.
In order to selectively bypass the registration step, our code would need to be able to recognize the difference between a hang and a valid registration. 
We gotta be careful here; I don't wanna write something that will break support for other browsers. 
EDIT: This is only a concern if a serviceworker is in place. 
It goes without saying that I do not expect to load a fresh service worker and the initial files on a near-dead network.
Background
Let's break down what navigator.serviceWorker.register() does, and what the promise that it returns represents.
The definitive set of steps is documented in the service worker specification, beginning with the "Start Register" job. There is slightly different behavior depending on whether the service worker scriptURL and scope have been previously registered or not, so the first thing to ask yourself is whether you're seeing this only when you're registering for the first time, in a "clean" browser, or whether you also see it when there's an identical, pre-existing service worker registration.
Either way, the promise that navigator.serviceWorker.register() returns fulfills once it's clear that the registration is for a script resource that's valid JavaScript with a scope that follows the security restrictions.
The promise will reject if you attempt to register a JavaScript file with invalid syntax, or if you use a scope that's not compatible with the service worker restrictions, or if there is a network error that prevents the JavaScript from being requested.
It's important to note that the way the navigator.serviceWorker.register() resolves does not reflect whether the service worker being registered actually installed correctly. It's possible to register a service worker script that does not contain any syntax errors and has a valid scope (so the navigator.serviceWorker.register() will fulfill), but which becomes redundant during the install phase, perhaps because the promise it passes to installEvent.waitUntil() ends up rejecting. The fact that navigator.serviceWorker.register() fulfills does imply that you'll always have an installed service worker!
Using navigator.serviceWorker.ready independently of registration
So, with that out of the way, let's get to your specific code snippet. If you're seeing the promise returned by navigator.serviceWorker.register() rejected after a long delay when you have a flaky network connection, it's almost certainly being rejected because the network request for the service worker script ends up timing out. There's really not much you could do about this scenario—that's the definition of what happens when you have a flaky network, and to ensure that your service worker is always "fresh", the request for the service worker script will bypass any caches by default.
I think the problem you're running into, though, is that you're using navigator.serviceWorker.ready execute some code once there's an active service worker, but you're only calling it if the navigator.serviceWorker.register() promise fulfills. For the reasons explained above, I don't think you should structure your code that way.
Instead, I would just break up your code into two steps—one that calls navigator.serviceWorker.register(), and one that waits for navigator.serviceWorker.ready to fulfill, independent of each other.
navigator.serviceWorker.register()
.then(() => console.log('Registration succeeded.')
.catch((error) => console.log('Registration failed: ', error));
navigator.serviceWorker.ready.then(() => {
// Put whatever code requires an active service worker here.
});

Service Worker gets deleted and does not receive push event even after specifying root scope while registering

I am registering the service worker by using the Service-Worker-Allowed header with '/' value and specifying scope while registration.
Registering S/W:
navigator.serviceWorker.register('/myapp/js/serviceWorker.js', {scope: '/'})
^ This is successful.
Serving the S/W:
res.setHeader('Service-Worker-Allowed', '/');
The workflow is something like below, myapp is not directly called but is done behind the scenes without user seeing actual url.
User navigates to
www.domain.com/otherapp?callmyapp=true
and behind the scenes myapp www.domain.com/myapp is initiated where I am doing above registrations with root scope.
Once user navigates to
www.domain.com/otherapp/somethingelse&myapp=false
myapp is not available to the user to interact.
Earlier (while NOT using root scope) the service worker was getting redundant but now it is not. However it is marked deleted.
I don't receive any push event after this redirection.
serviceWorker.js
self.addEventListener("push", function (event) {
console.log('sw push event');
if (event.data) {
const notificationData = JSON.parse(event.data.text());
self.registration.showNotification(notificationData.title, notificationData);
}
});
From what i understand with scopes is if i register the service worker with root scope it should still be available to receive push event and not get marked deleted.
I see many posts around service worker and angular, might be worth mentioning that myapp is old school backbone/vanilla/dust JS while otherapp is Angular.
ANSWER:
The issue was the otherApp was un-subscribing all the service workers. Only if i knew. :/ It took a while for me to debug and conclude from the logs even after explicitly caching myApp files.
It isn't clear what you mean by "behind the scenes" or "not available to the user to interact". The best way to get help with these kinds of issues is to create a reduced example, as in something which demonstrates the bug with all the irrelevant stuff removed.
I've tried to recreate the system you're talking about. https://glitch.com/edit/#!/sw-scope-example?path=server.js:22:0.
If you visit https://sw-scope-example.glitch.me/install-service-worker/ it calls:
navigator.serviceWorker.register('/deep/path/to/sw.js', { scope: '/' });
That service worker has a header:
Service-Worker-Allowed: /
The service worker is really basic:
addEventListener('fetch', (event) => {
event.respondWith(new Response('This is a response from the service worker.'));
});
It registers fine. Now if you visit any URL on the origin, such as https://sw-scope-example.glitch.me/other-app/whatever, you'll see the response from the service worker.
This works fine, so there's some key difference between your example & mine. Can you remix the Glitch above so it demonstrates the issue you're seeing?

Passing state info into a service worker before `install`

Background
I'm new to service workers but working on a library that is intended to become "offline-first" (really, almost "offline-only") (FWIW, the intent is to allow consumers of the library to provide JSON config representing tabular multilinear texts and get in return an app which allows their users to browse these texts in a highly customizable manner by paragraph/verse ranges.)
Other projects are to install the library as a dependency and then supply information via our JavaScript API such as the path of a JSON config file indicating the files that our app will consume to produce an (offline) app for them.
While I know we could do any of the following:
require users provide a hard-coded path from which our service worker's install script could use waitUntil with its own JSON request to retrieve the user's necessary files
skip the service worker's install step of the service worker for the JSON file, and rely on fetch events to update the cache, providing a fallback display if the user completed the install and went offline before the fetches could occur.
Post some state info from our main script to a server which the service worker, once registered, would query before completing its install event.
...but all choices seems less than ideal because, respectively:
Our library's consumers may prefer to be able to designate their own location for their JSON config.
Given that the JSON config designates files critical to showing their users anything useful, I'd rather not allow an install to complete only to say that the user has to go back online to get the rest of the files if they were not able to remain online after the install event to see all the required fetches occur.
Besides wanting to avoid more trips to the server and extra code, I'd prefer for our code to be so offline-oriented as to be able to work entirely on mere static file servers.
Question:
Is there some way to pass a message or state information into a service worker before the install event occurs, whether as part of the query string of the service worker URL, or through a messaging event? The messaging event could even technically arrive after the install event begins as long as it can occur before a waitUntil within the install is complete.
I know I could test this myself, but I'd like to know what best practices might be anyways when the critical app files must themselves be dynamically obtained as in such libraries as ours.
I'm guessing indexedDB might be the sole alternative here (i.e., saving the config info or path of the JSON config to indexedDB, registering a service worker, and retrieving the indexedDB data from within the install event)? Even this would not be ideal as I'm letting users define a namespace for their storage, but I need a way for it too to be passed into the worker, or otherwise, multiple such apps on the origin could clash.
Using a Query Parameter
If you find it useful, then yes, you can provide state during service worker installation by including a query parameter to your service worker when you register it, like so:
// Inside your main page:
const pathToJson = '/path/to/file.json';
const swUrl = '/sw.js?pathToJson=' + encodeURIComponent(pathToJson);
navigator.serviceWorker.register(swUrl);
// Inside your sw.js:
self.addEventListener('install', event => {
const pathToJson = new URL(location).searchParams.get('pathToJson');
event.waitUntil(
fetch(pathToJson)
.then(response => response.json())
.then(jsonData => /* Do something with jsonData */)
);
});
A few things to note about this approach:
If you fetch() the JSON file in your install handler (as in the code sample), that will effectively happen once per version of your service worker script (sw.js). If the contents of the JSON file change, but everything else stays the same, the service worker won't automatically detect that and repopulate your caches.
Following from the first point, if you work around that by, e.g., including hash-based versioning in your JSON file's URL, each time you change that URL, you'll end up installing a new service worker. This isn't a bad thing, per se, but you need to keep it in mind if you have logic in your web app that listens for service worker lifecycle events.
Alternative Approaches
You also might find it easier to just add files to your caches from within the context of your main page, since browsers that support the Cache Storage API expose it via window.caches. Precaching the files within the install handler of a service worker does have the advantage of ensuring that all the files have been cached successfully before the service worker installs, though.
Another approach is to write the state information to IndexedDB from the window context, and then read from IndexedDB inside of your service worker's install handler.
Update 3:
And since it is not supposed to be safe to rely on globals within the worker, my messaging solution seems even less sound. I think it either has to be Jeff Posnick's solution (in some cases, importScripts may work).
Update 2:
Although not directly related to the topic of this thread relating to "install" event, as per a discussion starting at https://github.com/w3c/ServiceWorker/issues/659#issuecomment-384919053 , there are some issues, particularly with using this message-passing approach for the activate event. Namely, the activate event may never fail, and thus never be tried again, leaving one's application in an unstable state. (A failure of install will at least not apply the new service worker to old pages, whereas activate will keep fetches on hold until the event completes, which it may never do if it is left waiting for a message that was not received, and which anything but a new worker will fail to correct since new pages won't be able to load to send that message again.)
Update:
Although I got the client from within the install script in Chrome, I wasn't able to receive the message back with navigator.serviceWorker.onmessage for some reason.
However, I was able to fully confirm the following approach in its place:
In the service worker:
self.addEventListener('install', e => {
e.waitUntil(
new Promise((resolve, reject) => {
self.addEventListener('message', ({data: {
myData
}}) => {
// Do something with `myData` here
// then when ready, `resolve`
});
})
);
});
In the calling script:
navigator.serviceWorker.register('sw.js').then((r) => {
r.installing.postMessage({myData: 100});
});
#JeffPosnick 's is the best answer for the simple case I described in the OP, but I thought I'd present my discovering that one can get messages from and into a service worker script early (tested on Chrome) by such as the following:
In the service worker:
self.addEventListener('install', e => {
e.waitUntil(self.clients.matchAll({
includeUncontrolled: true,
type: 'window'
}).then((clients) => new Promise((resolve, reject) => {
if (clients && clients.length) {
const client = clients.pop();
client.postMessage('send msg to main script');
// One should presumably be able to poll to check for a
// variable set in the SW message listener below
// and then `resolve` when set
// Despite the unreliability of setting globals in SW's
// I believe this could be safe here as the `install`
// event is to run while the main script is still open.
}
})));
});
self.addEventListener('message', e => {
console.log('SW receiving main script msg', e.data);
e.ports[0].postMessage('sw response');
});
In the calling script:
navigator.serviceWorker.addEventListener('message', (e) => {
console.log('msg recd in main script', e.data);
e.source.postMessage('sending back to sw');
});
return navigator.serviceWorker.register(
'sw.js'
).then((r) => {
// navigator.serviceWorker.ready.then((r) => { // This had been necessary at some point in my testing (with r.active.postMessage), but not working for me atm...
// Sending a subsequent message
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = (e) => {
if (e.data.error) {
console.log('err', e.data.error);
} else {
console.log('data', e.data);
}
};
navigator.serviceWorker.controller.postMessage('sending to sw', [messageChannel.port2]);
// });
});

Service worker fetch event handleR with spring MVC

I am trying a make a spring MVC app with offline features.Everything is working fine except the fetch handler.
Problem:
I am able to successfully register my service worker but when it comes to fetch handler it ain't working.
Things I tried:
Used debugger above and below the fetch handler.I found out that it is never being called. I am unable to find out the reason behind this strange behaviour.
self.addEventListener('install', function(event) {
console.log('The service worker is being installed.');
event.waitUntil(precache());
});
self.addEventListener('fetch', function(event) {
alert("Hi");
});
"a service worker registration ties the provided script URL to a scope, which is subsequently used for navigation matching."
What you're probably doing is
navigator.serviceWorker.register( '{SOME_RESOURCE_PATH}/service-worker.js') or even setting scope: './'. The problem with this is that is it will make your fetch only trigger on https://foo.bar/{SOME_RESOURCE_PATH}/. Meaning that you will need to set the sw onto the root of your site to remove the resource path being needed. What you'll need to do is change the config to put the sw on the root of the site.
ex. of what you can do in the mvc-config.xml
<mvc:resources mapping="/**" location="classpath:/PATH_TO_SW/"/>
then registering the sw would look like
navigator.serviceWorker.register( '/service-worker.js')

Categories

Resources