PWA - service-worker JS in subdirectory - service-worker

I tried to have my sw.js file in a subdirectory instead of the root directory. But then I get an error that says the service-worker could not be installed.
Is it really necessary to have this file in the root-directory or am I just missing something?
JS-Code:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/js/pwa/sw.js', { scope: '/js/pwa' }).then(function(reg) {
console.log('Successfully registered. Scope is ' + reg.scope);
}).catch(function(error) {
console.log('Registering failed ' + error);
});
} else {
console.log('Service worker can not be registered on this device');
}

If I did the same thing, I got the following error
Registering failed SecurityError: Failed to register a ServiceWorker: The path of the provided scope ('/js/pwa') is not under the max scope allowed ('/js/pwa/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
The following parts seemed strange
{ scope: '/js/pwa' }
Please do as follows
{ scope: '/js/pwa/' }

Related

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

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

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.

I can not precache with workbox

I can not precache with workbox.
Now I am trying to cache in workbox's injectManifest mode.
It has been confirmed that the runtime cache described in the file specified by swSrc is working.
However, files named with globDirectory and globPatterns are not precached. Specifically, specifying globPatterns: ['** / *. {Js, css, html, png}'] results in an error.
Please tell me how to get rid of this error.
workbox uses workbox-build.
The following shows each version.
workbox-build: 3.6.3
node: 11.11
It is running on localhost.
injectManifest.js
const workboxBuild = require('workbox-build');
async function injectManifest() {
try {
await workboxBuild
.injectManifest({
globDirectory: DIST_PUBLIC,
globPatterns: ['**/*.{js,css,html,png}'],
swSrc: path.join(DIST_PUBLIC, 'sw.template.js'),
swDest: path.join(DIST_PUBLIC, 'sw.js'),
})
.then(() => {
console.log('Service worker has been generated.');
});
} catch (e) {
console.log(e);
}
}
injectManifest();
sw.template.js
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
workbox.core.skipWaiting();
workbox.routing.registerRoute(new RegExp('.*.*'), new workbox.strategies.staleWhileRevalidate());
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
Error
AssertionError [ERR_ASSERTION]: Unable to find a place to inject the manifest. Please ensure that your service worker file contains the following:/(\.precacheAndRoute\()\s*\[\s*\]\s*(\)|,)/
at Object._callee$ (/Users/hoge/web/node_modules/workbox-build/build/entry-points/inject-manifest.js:82:13)
at tryCatch (/Users/hoge/web/node_modules/babel-runtime/node_modules/regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/Users/hoge/web/node_modules/babel-runtime/node_modules/regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (/Users/hoge/web/node_modules/babel-runtime/node_modules/regenerator-runtime/runtime.js:114:21)
at step (/Users/hoge/web/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
at /Users/hoge/web/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13
Please tell me how to get rid of this error.
This error indicates that injectManifest does not know where to inject the list of resources to be pre-cached in your service worker.
Quoting the documentation:
When workbox injectManifest is run, it looks for a specific string
(precaching.precacheAndRoute([]) by default) in your source service
worker file. It replaces the empty array with a list of URLs to
precache and writes the service worker file to its destination
location, based on the configuration options in config.js. The rest
of the code in your source service worker is left untouched.
So, most likely, all you have to do get rid of this error is to add the expected line in your service worker template:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
workbox.core.skipWaiting();
workbox.precaching.precacheAndRoute([]); // URLs to precache injected by workbox build
workbox.routing.registerRoute(new RegExp('.*.*'), new workbox.strategies.staleWhileRevalidate());
} else {
console.log(`Boo! Workbox didn't load 😬`);
}

Manifest start_url is not cached by a Service Worker

I'm using Lighthouse to audit my webapp. I'm working through the failures, but I'm stuck on this one:
Failures: Manifest start_url is not cached by a Service Worker.
In my manifest.json I have
"start_url": "index.html",
In my worker.js I am caching the following:
let CACHE_NAME = 'my-site-cache-v1';
let urlsToCache = [
'/',
'/scripts/app.js',
'/index.html'
];
Which lines up with what I see in the Application tab in Chrome Dev tools:
So... why is it telling me start_url is not cached?
Here is my full worker.js file:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/worker.js').then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
let CACHE_NAME = 'my-site-cache-v1.1';
let urlsToCache = [
'/',
'/scripts/app.js',
'/index.html'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
Let's look at Lighthouse's source code
static assessOfflineStartUrl(artifacts, result) {
const hasOfflineStartUrl = artifacts.StartUrl.statusCode === 200;
if (!hasOfflineStartUrl) {
result.failures.push('Manifest start_url is not cached by a service worker');
}
}
We can notice, that it's not checking your cache, but response of the entry point. The reason for that must be that your service worker is not sending proper Response on fetch.
You'll know that it's working, if in DevTools, in your first request, there'll be (from ServiceWorker) in size column:
There're two problems with the code you've provided:
First one is that you're messing service worker code with service worker registration code. Service worker registration code should be the code executed on your webpage.
That code should be included on your page:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/worker.js').then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
and the rest of what you've pasted should be your worker.js code. However service worker get installed, because you've files in cache, so I suspect you just pasted this incorrectly.
The second (real) problem is that service worker is not returning this cached files. As I've proved earlier, that error from lighthouse means that service worker is not returning start_url entry file.
The most basic code to achieve that is:
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request));
});
Service worker is event-driven, so when your page wants to get some resource, service worker reacts, and serves the one from cache. In real world, you really don't want to use it like that, because you need some kind of fallback. I strongly recommend reading section Serving files from the cache here
Edit: I've created pull request in Lighthouse source code to clarify that error message
It seems to be that Chrome lighthouse (chrome v62) performs a generic fetch(). See discussion on https://github.com/GoogleChrome/lighthouse/issues/2688#issuecomment-315394447
In my case, an offline.html is served after an "if (event.request.mode === 'navigate'){".
Due to the use of lighthouse´s generic fetch(), lighthouse will not get served this offline.html, and shows the "Manifest start_url is not cached by a Service Worker" error.
I solved this problem by replacing:
if (event.request.mode === 'navigate'){
with
if (event.request.method === 'GET' ){

navigator.serviceWorker is never ready

I registered a service worker successfully, but then the code
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
// Do we already have a push message subscription?
....
hangs -- the function is never called. Why?
The problem was that the service-worker.js file was stored in an assets sub-directory.
Don't do that: store the service-worker.js in the root of your app (or higher). That way your app can access the service-worker.
See HTML5Rocks article --
One subtlety with the register method is the location of the service worker file. You'll notice in this case that the service worker file is at the root of the domain. This means that the service worker's scope will be the entire origin. In other words, this service worker will receive fetch events for everything on this domain. If we register the service worker file at /example/sw.js, then the service worker would only see fetch events for pages whose URL starts with /example/ (i.e. /example/page1/, /example/page2/).
Like said in the accepted answer, the problem is, indeed, probably because your service worker JS file is in a different path than your current page.
By default, the scope of the service worker is the path to its JS file. If your JS file is reachable at http://www.example.com/assets/js/service-worker.js, your service worker will only work/"be ready" for URL starting with /assets/js/.
But, you can change the scope of the service worker. First, you need to register if using the scope option:
navigator.serviceWorker.register('http://www.example.com/assets/js/service-worker.js', {
scope: '/',
});
If you do, just this, you will get errors in the Chrome console:
The path of the provided scope ('/') is not under the max scope
allowed ('/assets/js/'). Adjust the scope, move the Service Worker
script, or use the Service-Worker-Allowed HTTP header to allow the
scope.
/admin/#/builder:1 Uncaught (in promise) DOMException: Failed to
register a ServiceWorker for scope ('http://www.example.com/') with
script
('http://www.example.com/assets/js/service-worker.js'): The path of
the provided scope ('/') is not under the max scope allowed
('/assets/js/'). Adjust the scope, move the Service Worker script, or
use the Service-Worker-Allowed HTTP header to allow the scope.
You then need to create an .htacess file at the root of your website with the following content:
<IfModule mod_headers.c>
<Files ~ "service-worker\.js">
Header set Service-Worker-Allowed: /
</Files>
</IfModule>
Had the same issue, but putting service worker and installing script in the same directory didn't solve this. For me the solution was to add a "/" to the end of the url.
So i had:
http://localhost:9105/controller/main.js - installing script
http://localhost:9105/controller/sw.js - service worker
http://localhost:9105/controller/index.html - page
And when the url in the browser was http://localhost:9105/controller service worker have never been ready, but when url is http://localhost:9105/controller/ it works fine.
I used code below to control this
if (!window.location.href.endsWith('/')) {
window.location.assign(window.location.href + '/')
}
Add service-worker.js file to your project root directory
You can download service-worker.js file from Link
And use below code to register service worker.
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js', { scope: './' })
.then(function (registration) {
console.log("Service Worker Registered");
})
.catch(function (err) {
console.log("Service Worker Failed to Register", err);
})
}

Resources