File location of Service Worker affect the accessibility for registration - javascript

I am trying to create a PWA with reactjs. There is no service worker when I use create-react-app so I created one myself.
The simplified file structure is this:
-public
-index.html
-manifest.json
-**service-worker.js**
-src
-index.js
-serviceWorkerRegistration.js
The entry point of the app is index.js, which will import serviceWorkerRegistraion.js. Then serviceWorkerRegistration will run the following code:
if ("serviceWorker" in navigator) {
console.log("support service worker");
navigator.serviceWorker.register("/service-worker.js") // Line A
.then(reg => console.log("service worker registered", reg))
.catch(err => console.warn("service worker registration failed", err));
}
else console.log("service worker not supported");
And the service worker is registered successfully.
However, if I move service-worker.js to the src directory and change the file name in Line A to "./service-worker.js", it seems service-worker.js cannot be found and throw an error with The script has an unsupported MIME type ('text/html') (which happens when the file cannot be found and a html file is returned instead)
Why the location of service-worker.js affect the accessibility for registrating service worker in this way? Is there any way I can access the file with service-worker.js living in the src directory?

Related

Browser loading unrelated (non-existent) file when registering a service worker in an extension

I'm registering a service worker in an extension, but getting a TypeError because the browser is trying to load my service worker file as a path of the website in addition to my extension's file.
Registering like:
navigator.serviceWorker.register('swerk.js', {scope: '.'})
along with a then and catch for the promise in a main.js (and swerk.js is included in the manifest content_scripts).
In swerk.js, I just have a print to indicate when the worker is loaded.
When the extension is loaded and the service worker is registered, the print does succeed. However, the browser also attempts to load /swerk.js, which of course doesn't exist because I don't own the host and swerk.js is in my extension, so I get a TypeError:
extension loaded main.js:2:13
sworker loaded! swerk.js:1:9
Service worker registration failed: TypeError: ServiceWorker script at <URL>/swerk.js for scope <URL> encountered an error during installation. main.js:8:21
and the promise's catch is called as expected because of the error. But why's <URL>/swerk.js being loaded?!
Using Firefox 109.0.
Are you sure you also have to include the swerk.js in the content-script? As it is already loaded by your sw registerer script.

Service worker does not cache index.html served by golang server on '/'

I'm playing around with a service worker trying to cache my index.html. I'm serving it via
http.HandleFunc("/sw", handle.SW)
http.HandleFunc("/", handle.Index)
My service worker cache looks like this
self.addEventListener('install', (event) => {
self.skipWaiting();
event.waitUntil(async function () {
const cache = await caches.open(CACHE_VERSION);
await cache.addAll([
'/',
'/static/merged.css',
'/static/merged.js',
'/favicon.ico'
]);
}());
});
Without the service worker my network tab in the developer tools shows the size of the index.html with 4.5 KB and a time of 46ms.
Now with the service worker merged.css, merged.js and favicon.ico are cached, but my index has two entries. One from service worker, and one of size 4.5 KB and a time of 48ms.
So I'm guessing that index.html is not cached and I can not find out how to do it.
From the screenshot you uploaded, it says that the request for fetching the index.html is initiated by a script named sw.js, and the exact code is placed at the second line. If this is truly the service-worker in your code, please recheck it carefully and find out why this request is made.

PWA serviceWorker not always registered

We have in-house built CMS and recently we added PWA to it. Now, when accessing the home page www.ourdomain.com everything is fine, but when accessing an article, we are a news website, someting like www.ourdomain.com/section/article a message appears:
Failed to register a ServiceWorker: A bad HTTP response code (404) was
gwreceived when fetching the script.
First the scope was
./
and in that case ServiceWorker wasn't registered at all.
Now I changed the scope to
/
and not it is registered for the home page but not for any article. I went through docs, reading questions and answers but doesn't seem able to solve this. By they way, PWA installs well on a mobile an works well.
Your service worker js file should preferably be in your root public directory of your app since you're just starting out with PWAs, and specifying the correct scope is essential, Here's a perfect example of installing your service worker from Google PWA guide.
main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Registration successful, scope is:', registration.scope);
})
.catch(function(error) {
console.log('Service worker registration failed, error:', error);
});
}
service-worker.js
Expecting that you have /service-worker.js and main.js in your root directory, and if you get to work with manifest, you render the same scope as your start_url
You can try the above installation snippet on your app public root directory.

An unknown error occurred when fetching the script (Service Worker)

When going offline, I get the following error by my service worker:
(unknown) #3016 An unknown error occurred when fetching the script
my service worker looks like this:
var version = 'v1'
this.addEventListener('install', function(event){
event.waitUntil(
caches.open(version).then(cache => {
return cache.addAll([
'https://fonts.googleapis.com/icon?family=Material+Icons',
'https://fonts.googleapis.com/css?family=Open+Sans:400,600,300',
'./index.html'
])
})
)
})
this.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(resp) {
// if it's not in the cache, server the regular network request. And save it to the cache
return resp || fetch(event.request).then(function(response) {
return caches.open(version).then(function(cache) {
cache.put(event.request, response.clone())
return response
})
})
})
)
})
It is at top level directory, right next to a manifest importing like this in index.html:
<link rel="manifest" href="/manifest.json">
I import the service worker in my entry js file. And register it right after.
require('tether-manifest.json')
import serviceWorker from 'sw'
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(serviceWorker)
.then(() => {
// registration worked
}).catch(error => {
throw new Error(error)
})
}
It registers fine. I don't encounter the error until I go offline.
I am using webpack with React, and doing the following in webpack to copy my sw.js file to the dist folder:
loaders: [
{ // Service worker
test: /sw\.js$/,
include: config.src,
loader: 'file?name=[name].[ext]'
},
{ // Manifest
test: /manifest\.json$/,
include: config.src,
loader: 'file?name=[name].[ext]'
}
]
The error doesn't give any information as to what is happening.
Anyone have any idea how to fix this?
I had exactly the same problem, I spent an hour trying to figure it out and it turned out that I had another tab for the same origin left opened somewhere (so using the same shared Service Worker) that had the "Offline" checkbox left checked and that prevented another tabs from requesting sw.js for some reason.
It seems that the offline state is leaking from the tab within Service Worker scope while not being properly reflected nor managed by the other tabs than the one that was turned Offline first.
So make sure you have no other clients running the same Service Worker - you should be able to find them listed in DevTools > Application > Service Workers.
Before trying anything else, check whether your https certificate is invalid or not matching the url you are accessing.
E.g. In my case I was trying to access https://localhost with a certificate that was registered for another domain.
While hitting "proceed" would allow me to enter to the site, this exact error would be printed to the console:
An unknown error occurred when fetching the script
For me this error went away when i added sw.js to the cache upon install. Simply forgot to do that, but it solved the issue.
I had this issue while working in an Angular project. My problem was that I was using Angular CLI's built-in ng serve -prod command.
To make it work, I used ng build -prod and then host the resulting dist folder using http-server
In Chrome, what I did was to check the option Bypass for network and then I was able to reload.

Ruby on Rails & service worker

I'm trying to use a service worker in my Ruby on Rails application.
I need to use some erb features in my app/javascripts/service-worker.js.erb file. The service worker registration script looks like this:
var registerServiceWorker = function() {
navigator.serviceWorker.register(
'<%= asset_path('service-worker.js') %>',
{ scope: '/assets/' }
)
.then(function() {
console.info('Service worker successfully registered');
})
.catch(function(error) {
console.warn('Cannot register sercie worker. Error = ', error);
});
}
This does not work; I never get promise here:
navigator.serviceWorker.ready.then
I also tried ./ and / scopes but I got this error:
DOMException: Failed to register a ServiceWorker: The path of the
provided scope ('/') is not under the max scope allowed ('/assets/').
Adjust the scope, move the Service Worker script, or use the
Service-Worker-Allowed HTTP header to allow the scope.
If I move my service-worker.js to the public folder, remove the .erb extension and change scope to ./, everything works great, but I have no template engine there.
Any suggestions?
There's now a gem, serviceworker-rails that will allow you to proxy requests for serviceworker scripts in the asset pipeline. Because of the way browsers register serviceworker scripts, it's best to avoid caching them.
Sprockets will fingerprint assets and Rails (or your webserver) typically serves the compiled files with aggressive caching headers from the /assets directory.
What we want is the ability to customize service worker paths and response headers and still get Sprockets preprocessing, i.e. CoffeeScript/ES2015 to JavaScript transpilation.
The serviceworker-rails gem inserts middleware to your Rails stack that will proxy a request for /serviceworker.js (for example) to the corresponding fingerprinted, compiled asset in production. In development, you get the auto-reloading behavior you would expect. You can set up multiple serviceworkers at different scopes as well.
https://github.com/rossta/serviceworker-rails
Because of the security purpose, you can't register ServiceWorker in higher scope than from where it was executed.
If you really need template engine, you may try to dynamically load JS file from file in your /public folder (How do I include a JavaScript file in another JavaScript file?). Currently Service-Worker-Allowed HTTP header is not implemented yet in Firefox (https://bugzilla.mozilla.org/show_bug.cgi?id=1130101)
I'm running into this problem now as well. Based on my research, I think the proper way for a Rails application to handle service workers is to serve the service worker javascript file normally using the Asset pipeline, scope the service worker using the scope option (like you have done), and then use the Service-Worker-Allowed HTTP header to explicitly allow the new scope.
Unfortunately, at the moment there are several barriers to implementing this method:
Rails 4 (apparently) does not allow adding HTTP headers to Assets it serves, other than the cache-control header. To work around this you could add a Rack plugin as detailed in this S.O. answer, or in production if you are using an Asset server such as Nginx, you can get around this by having Nginx add the HTTP header. Though this doesn't help during development.
Rails 5 does allow adding HTTP headers to Assets however, as detailed in this blog post.
Browser support for service workers and the Service-Worker-Allowed HTTP header is currently poor.
Just do it in the root directory. For example, I put a route
get 'service-worker(.format)', to: 'core#service_worker'
Then just put in your controller (core_controller.rb in my example)
def service_worker
end
Then create app/views/core/service_worker.js.erb and put your JS there (including any <%= stuff %>).
I recently wrote an article about how to do that entirely with Webpacker. Please find my guide to a Progressive Web App with Rails.
In short:
add the webpacker-pwa npm package with yarn add webpacker-pwa and edit environment.js with:
const { resolve } = require('path');
const { config, environment, Environment } = require('#rails/webpacker');
const WebpackerPwa = require('webpacker-pwa');
new WebpackerPwa(config, environment);
module.exports = environment;
This will allow you to use service workers. Please check the README of the project as well: https://github.com/coorasse/webpacker-pwa/tree/master/npm_package
Leave the service worker file in whatever directory imposed by your project structure and set the scope option to / and add the Service-Worker-Allowed HTTP header to the response of your service worker file.
In ASP.Net, I added the following to web.config file:
<location path="assets/serviceWorker.js">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Service-Worker-Allowed" value="/" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
And register the worker by:
navigator.serviceWorker.register('/assets/serviceWorker.js', { scope: '/' })
.then(function (registration)
{
console.log('Service worker registered successfully');
}).catch(function (e)
{
console.error('Error during service worker registration:', e);
});
Tested on Firefox and Chrome.
Refer to the examples in Service worker specification

Categories