Fetch of the service worker doesn't seem to get triggered - service-worker

When a browser requests an image from the server, the call is getting picked up by an API controller in the back end. There, a authorization check must be done before returning the image in order to check if the request is allowed or not.
So I need to add the authorization header and when searching for the best solution, I found this article: https://www.twelve21.io/how-to-access-images-securely-with-oauth-2-0/ and I was mostly intereseted in the solution number 4 which uses a Service Worker.
I made my own implementation, I registered a serviceWorker:
if ('serviceWorker' in navigator) {
console.log("serviceWorker active");
window.addEventListener('load', onLoad);
}
else {
console.log("serviceWorker not active");
}
function onLoad() {
console.log("onLoad is called");
var scope = {
scope: '/api/imagesgateway/'
};
navigator.serviceWorker.register('/Scripts/ServiceWorker/imageInterceptor.js', scope)
.then(registration => console.log("ServiceWorker registration successful with scope: ", registration.scope))
.catch(error => console.error("ServiceWorker registration failed: ", error));
}
and this is in my imageInterceptor:
self.addEventListener('fetch', event => {
console.log("fetch event triggered");
event.respondWith(
fetch(event.request, {
mode: 'cors',
credentials: 'include',
header: {
'Authorization': 'Bearer ...'
}
})
)
});
When I run my application, I see in my console that the registration seems to be successfully executed as I see the console.logs printed (ServiceWorker active, onLoad is called and successful registration with correct scope: https://localhost:44332/api/imagesgateway/
But when I load an image (https://localhost:44332/api/imagesgateway/...) via the gateway, I still get a 400 and when put a breakpoint on the backend I see that the authentication header is still null. Also, I don't see "fetch event triggered" message in my console. In another article it is stated that I can see the registered service workers via this setting: chrome://inspect/#service-workers but I don't see my worker there either.
My question is: Why isn't the authorization header added? Is it because, although the registration seems to go successfully, this isn't actually the case and therefore I don't see the worker in inspect#service-workers either?

You're not seeing fetch event triggered in the browser console because your Service Worker script isn't allowed to intercept the image requests. This is because your Service Worker script is located in a directory outside the scope of the requests you're interested in.
In order to intercept requests that handle resources at
/api/imagesgateway/
the SW script needs to be located in either
/, /api/, or /api/imagesgateway/. It cannot be located in /some/other/directory/service-worker.js.
This is the reason that your Service Worker registers successfully! There is no probelm in registering the SW. The problem lies in what it can do.
More info: Understanding Service Worker scope

Related

are network requests due to code after a service worker is registered guaranteed to be served by the service worker fetch() handler?

Registering a service worker is done in index.html with (eg):
<script>
navigator.serviceWorker.register('/sw.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);
});
</script>
If that code is followed by something that requests a resource, eg:
<script src="a.js"></script>
is that request guaranteed to trigger the 'fetch' event handler in the service worker (and so, potentially, be served from a cache)?
Or, should any code that causes a network access in index.html be added dynamically in the then() callback of the register() function (and, is THAT then guaranteed to be served by the service worker's 'fetch' event handler)?
I would recommend reading through "The Service Worker Lifecycle" for more general information.
The answer to most of your questions is "no," since what you're talking about is the initial registration of the service worker. Registering a service worker kicks off an installation and activation process that's independent from the promise returned by register(). The only thing you could infer from that promise is whether starting the process succeeded or not.
What you're asking about—whether a fetch handler will be invoked or not—relies on a service worker being in control of the current page.
In terms of JavaScript, if you want to answer the question "is this page controlled by (any) service worker?", you can do that by checking whether or not navigator.serviceWorker.controller is undefined.
If you want to write code that will only execute once there's a service worker in control of the current page (with the caveat that it might never execute, if something prevented the service worker from properly activating), you could do that by creating a promise that will resolve immediately if there's already a controller, and will otherwise resolve once the controllerchange event fires:
const p = new Promise(r => {
if (navigator.serviceWorker.controller) return r();
navigator.serviceWorker.addEventListener('controllerchange', e => r());
});
// Later, if you want code to execute only if the page is controlled:
p.then(() => {
// There's a SW in control at this point.
});
Inside your service worker, you can add the following to your activate handler to ensure that as soon as a newly installed service worker activates, it takes control of any open pages (including the page that registered it for the first time):
self.addEventListener('activate', () => self.clients.claim());
If you don't include self.clients.claim() inside your service worker's activate handler, then the page that starts out uncontrolled will never start being controlled, even though it's registered a service worker that has activated.

What to change to prevent double request from service worker?

Please do not mark as duplicate. This is not an exact duplicate of the other similar questions here on SO. It's more specific and fully reproducible.
Clone this repo.
yarn && yarn dev
Go to localhost:3000 and make sure under (F12)->Applications->Service workers, the service worker is installed.
Go to Network tab and refresh a few times(F5)
Observe how the network requests are doubled.
Example of what I see:
Or if you want to do it manually follow the instructions below:
yarn create-next-app app_name
cd app_name && yarn
in public folder, create file called service-worker.js and paste the following code:
addEventListener("install", (event) => {
self.skipWaiting();
console.log("Service worker installed!");
});
self.addEventListener("fetch", (event) => {
event.respondWith(
(async function () {
const promiseChain = fetch(event.request.clone()); // clone makes no difference
event.waitUntil(promiseChain); // makes no difference
return promiseChain;
})()
);
});
open pages/index.js and just below import Head from "next/head"; paste the following code:
if (typeof window !== "undefined" && "serviceWorker" in navigator) {
window.addEventListener("load", function () {
// there probably needs to be some check if sw is already registered
navigator.serviceWorker
.register("/service-worker.js", { scope: "/" })
.then(function (registration) {
console.log("SW registered: ", registration);
})
.catch(function (registrationError) {
console.log("SW registration failed: ", registrationError);
});
});
}
yarn dev
go to localhost:3000 and make sure the service worker has been loaded under (F12)Applications/Service Workers
Go to the Network tab and refresh the page a few times. See how the service worker sends two requests for each one
What do I need to change in the service-worker.js code so that there are no double requests?
This is how Chrome DevTools shows requests and is expected.
There is a request for a resource from the client JavaScript to the Service Worker and a request from the Service Worker to the server. This will always happen unless the service worker has the response cached and does not need to check the server for an update.
Does not seems the right way to initialize service worker in Next.js.You may need to look into next-pwa plugin to do it right.Here is the tutorial PWA with Next.js
If anyone is looking for an answer to the original question 'What to change to prevent double request from service worker?', specifically for network requests.
I've found a way to prevent it. Use the following in the serviceworker.js. (This also works for api calls etc.)
self.addEventListener('fetch', async function(event) {
await new Promise(function(res){setTimeout(function(){res("fetch request allowed")}, 9999)})
return false
});

How to fix importScripts not defined using service worker and onesignal?

I am trying to set up a web push app, with one signal notifications. I know nothing about service workers, but used rails service-workers gem. I get this error >importScripts is not defined.
I have already followed this tutorial from rossta, serviceworker-rails.
The error must be in OneSignalSDKWorker.js.erb.
I have already tried to change the name to OneSignalSDKWorker.js
nothing seems to work. I'm working fully https on Heroku.
make a function
``` funtion(){
importScripts('https://cdn.onesignal.com/sdks/OneSignalSDKWorker.js');
};
```serviceworket-companion.js
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/serviceworker.js', { scope: './' })
.then(function(reg) {
console.log('[Companion]', 'Service worker registered!');
});
}
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/OneSignalSDKWorker.js', { scope: './' })
.then(function(reg) {
console.log('[Companion] Onesignal worker registered!');
});
}
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/OneSignalSDKUpdaterWorker.js', { scope: './' })
.then(function(reg) {
console.log('[Companion] Updater worker registered!');
});
}
```
``` OneSignalSDKworker.js.erb
importScripts('https://cdn.onesignal.com/sdks/OneSignalSDKWorker.js');
```
I hope to get user to subscribe in onesignal, but instead it only gives me that error!
I can see a couple potential issues.
1) If you are attempting to importScripts from a service worker, that needs to be executed at the top of the file and not in a method. In other words, it needs to be the first thing that runs within your service worker.
2) You are attempting to register multiple services workers which works but only if they are defined for different scopes. In the code you provided, you are registering them for the same scope which will only register one of them.

Service Worker: Serve offline.html instead when offline

I am trying to set up my site service worker to display an offline.html file when offline instead of whichever HTML file the user was trying to fetch that was not in the cache.
Following the Workbox docs (https://developers.google.com/web/tools/workbox/guides/advanced-recipes#provide_a_fallback_response_to_a_route), I wrote the code below, but whenever I tick the offline checkbox in Chrome DevTools and visit an HTML page to test it, I get Chrome's standard "No internet" dinosaur page.
workbox.precaching.precacheAndRoute([
'/offline.html'
]);
workbox.routing.setCatchHandler(({ event }) => {
switch (event.request.destination) {
case 'document':
return caches.match(workbox.precaching.getCacheKeyForURL('/offline.html'));
break;
default:
return Response.error();
}
});
You forgot to register a route. Hence the workbox.routing.setCatchHandler function is never invoked.
Adding this code to your Service Worker should solve the issue
workbox.routing.registerRoute(
new RegExp('.html'),
new workbox.strategies.NetworkOnly({
cacheName: 'htmlcache'
})
);
You can also refer to this example:
https://progressify.org/building-a-pwa-with-an-offline-fallback-page-using-workbox/

service worker install event is called before register event is completed

I have attached install event to service worker as below. But Install event fired before register event is completed. See code snippets and console logs below.
My concern is how install event is fired before register event is completed?
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js',{scope : '/'}).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);
});
}
var cacheName = 'V-1';
var filesToCache = [
'/', '/index.html',
'/css/all.css', '/css/material.min.css',
'/js/all.js', '/js/material.min.js',
'/images/service-worker-1.png','/images/service-worker-2.png','/images/service-worker-3.png',
];
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Installing');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
return cache
.addAll(filesToCache) //this is atomic in nature i.e. if any of the file fails the entire cache step fails.
.then(() => {console.log('[ServiceWorker] App shell Caching Successful');})
.catch(() => {console.log('[ServiceWorker] App shell Caching Failed');})
})
);
});
navigator.serviceWorker.register() is not an event. It's a function that returns a promise, and then promise will resolve with a ServiceWorkerRegistration object that corresponds to the registration.
The actual service worker logic is executed in a different thread/process, and the lifecycle events that the service worker handles, like the install event, happen independently of the web page that registered the service worker. What you're seeing in your console.log() output is expected.
If you want to keep track of the state of the service worker from your web page, you can add event listeners to the ServiceWorkerRegistration object. There's an example of this at https://googlechrome.github.io/samples/service-worker/registration-events/index.html
If you want to write code that will cause your web page to wait until there's an active service worker before it takes some action, you could make use of the navigator.serviceWorker.ready promise.

Resources