I can't seem to figure out any reason why a service worker would be deleted with the code that I have that registers or actually is the service worker.
But in this site, it shows up as deleted in the Service Workers section of the chrome dev tools (image below).
Yet it is also registering properly as logged in the console (same image below).
Here is the service worker registration code:
if('serviceWorker' in navigator){
navigator.serviceWorker.register('/earnie.min.js', { scope: '/'}).then(function(registration){
console.log('Registration successful, scope is:', registration.scope);
}).catch(function(error){
console.log('Service worker registration failed, error:', error);
});
}
Here is the service worker code:
var cachename="e2",cachelist=";;.;/;./;/privacyPolicy.html;/css/main.css;/css/normalize.css;".split(";");
self.addEventListener("install",function(a){
a.waitUntil(caches.open(cachename).then(function(a){
return a.addAll(cachelist)
}))
});
self.addEventListener("fetch",function(a){
a.respondWith(caches.match(a.request).then(function(b){
return b?b:fetch(a.request.clone(), { credentials: 'include', redirect: 'follow' })
}))
});
What is causing it to be deleted and not registering?
Registration succeeded, but installation actually fails. Your waitUntil() promise is not resolving, which causes InstallEvent event to fail, thus deleting the ServiceWorker. cachelist probably returns invalid/empty values when you run split(';')
I recommend ensuring that cachelist is an array with valid URI values, then you can debug within the install event**
self.addEventListener("install", event => {
event.waitUntil(
caches.open(cachename)
.then(cache => cache.addAll(cachelist))
.catch(error => console.error('đ©', error))
)
})
**You'll most likely need "Preserve log" console option enabled in Chrome Dev Tools to see the console error.
Related
I have a set of integration tests which run in Karma. Unfortunately, they call out to an external, production API endpoint. I do not want integration tests to call out and am exploring my options.
I am wondering if service workers are a viable solution. My assumption is that they do not work because https://github.com/w3c/ServiceWorker/issues/1188 makes it clear that cross-origin fetch is not supported and localhost is not the same origin as a production API endpoint.
For clarity, here is some code I am running:
try {
const { scope, installing, waiting, active } = await navigator.serviceWorker.register('./base/htdocs/test/imageMock.sw.js');
console.log('ServiceWorker registration successful with scope: ', scope, installing, waiting, active);
(installing || waiting || active).addEventListener('statechange', (e) => {
console.log('state', e.target.state);
});
} catch (error) {
console.error('ServiceWorker registration failed: ', error);
}
and the service worker
// imageMock.sw.js
if (typeof self.skipWaiting === 'function') {
console.log('self.skipWaiting() is supported.');
self.addEventListener('install', (e) => {
// See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-skipwaiting
e.waitUntil(self.skipWaiting());
});
} else {
console.log('self.skipWaiting() is not supported.');
}
if (self.clients && (typeof self.clients.claim === 'function')) {
console.log('self.clients.claim() is supported.');
self.addEventListener('activate', (e) => {
// See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#clients-claim-method
e.waitUntil(self.clients.claim());
});
} else {
console.log('self.clients.claim() is not supported.');
}
self.addEventListener('fetch', (event) => {
console.log('fetching resource', event);
if (/\.jpg$/.test(event.request.url)) {
const response = new Response('<p>This is a response that comes from your service worker!</p>', {
headers: { 'Content-Type': 'text/html' },
});
event.respondWith(response);
}
});
and when this code is ran I see in the console
ServiceWorker registration successful with scope: http://localhost:9876/base/htdocs/test/ null null ServiceWorker
and then requests to https://<productionServer>.com/image.php are not intercepted by the fetch handler.
Is it correct that there is no way to intercept in this scenario?
You can use a service worker to intercept requests made by a browser as part of a test suite. As long a service worker is in control of a web page, it can intercept cross-origin requests and generate any response you'd like.
(The issue you link to about "foreign fetch" is something different; think of it as the remote production server deploying its own service worker. This was abandoned.)
"Stop mocking fetch" is a comprehensive article covering how to use the msw service worker library within the context of a test suite.
I can't say off the top of my head exactly why your current setup isn't working, but from past experience, I can say that the most important thing to remember when doing this is that you need to delay making any requests from a client test page until the page itself is being controlled by an active service worker. Otherwise, there's a race condition in which you might end up firing off a request that needs to trigger a fetch handler, but won't if the service worker isn't yet in control.
You can wait for this to happen with logic like:
const controlledPromise = new Promise((resolve) => {
// Resolve right away if this page is already controlled.
if (navigator.serviceWorker.controller) {
resolve();
} else {
navigator.serviceWorker.addEventListener('controllerchange', () => {
resolve();
});
}
});
await controlledPromise;
// At this point, the page will be controlled by a service worker.
// You can start making requests at this point.
Note that for this use case, await navigator.serviceWorker.ready will not give you the behavior you need, as there can be a gap in time between when the navigator.serviceWorker.ready promise resolves and when the newly activated service worker takes control of the current page. You don't want that gap of time to lead to flaky tests.
When running a local server from Visual Studio, Firefox will error out and not load the page correctly. It shows this error:
Failed to load âhttps://localhost/js/mqtt.jsâ. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value âundefinedâ. serviceworker.js:19:10
it works fine on Chrome. It also works fine on Firefox when the server is on a cloud-based Azure server. Here is the code for the service worker:
// Service worker file for PWA
var CACHE_NAME = 'v5';
var urlsToCache = [
'/index.html'
];
self.addEventListener('install', function (event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).then(response => {
cache.put(event.request, response.clone());
return response;
}).catch(_ => {
return caches.match(event.request);
})
)
});
I am unsure what this error is caused by. One workaround is to go to "about:debugging#workers" and unregister the serviceworkers manually. Then refreshing the page will allow it to load correctly. However I need a solution, not a workaround.
Based on that code, I'd expect to see that error message if the following two conditions are true:
fetch(event.request) rejects.
caches.match(event.request) results in a cache miss, which causes the promise to resolve with undefined.
This isn't really out of the ordinaryâit's just the logic you've written in your service worker, and the behavior depends on both the current network/server conditions, as well as the state of your local cache.
The same mistake happened to me. The problem was that I had CORS disabled for some route on the server.
Enabling CORS on the server for all routes solved it.
// Enable cors for all routes
app.use(cors(corsOptions));
// To enable cors only for a single route must be added on the app.get
// app.get('/', cors(corsOptions), nextCallback() )
I have a case where I need to catch a rejected promise from a Service Worker install event and display an error message.
I've set up a test Service Worker called testsw.js which immediately throws a rejected promise, like so:
console.log("Hello from SW");
self.addEventListener('install', function (evt) {
evt.waitUntil(Promise.reject(new Error("Test")));
})
and I'm registering the Service Worker using the following code:
navigator.serviceWorker.register('/testsw.js')
.then(function (reg) {
console.log("Service worker successfully registered.", reg);
})
.catch(function (err) {
console.error("Service worker failed to register.", err);
})
The catch function is never hit in this case, and registering the Service worker results in this console output:
testsw.js:1 Hello from SW
testsw.js:3 Uncaught (in promise) Error: Test
at testsw.js:3
(anonymous) # testsw.js:3
(index):468 Service worker successfully registered. ServiceWorkerRegistration
I've already tried wrapping the rejected promise in a function and calling that in evt.waitUntil(), as well as throwing a plain Error, with no change. The Service Worker is still put into the redundant state, which is good, but I still need to know that the installation failed.
Am I misunderstanding how rejected promises work in a Service Worker installation? Is there something misconfigured in the registration that would make it always hit the then block, even if the promise is rejected? Failing that, is there something in the ServiceWorkerRegistration class or somewhere else that would tell us that the service worker failed to install or update?
If it makes a difference, this was tested in Chrome 77 and Edge 83 with no success.
The registration promise fulfills as soon as the script has been loaded and the worker has been registered, regardless whether it fails installation or not (it might not even need to be installed any more). To check whether the installation failed, you need to listen to statechange events from the installing worker:
navigator.serviceWorker.register('/testsw.js').then(function (reg) {
console.log("Service worker successfully registered.", reg);
if (reg.installing) {
reg.installing.onstatechange = function(e) {
if (e.target.state == 'installed') {
console.log("Service worker successfully installed.");
} else if (e.target.state == 'redundant') {
console.log("Service worker failed to install.");
}
};
}
}).catch(function (err) {
console.error("Service worker failed to register.", err);
});
If you want to also display the cause of the failed installation, it seems you have to do that yourself by handling the error in the install handler and pass a message to originating page. Maybe also try listening to error events on the installing worker. You might however have to fire these manually, as unhandled promise rejections don't get caught automatically:
console.log("Hello from SW");
self.addEventListener('install', function (evt) {
const installation = Promise.reject(new Error("Test"));
evt.waitUntil(installation);
installation.catch(err => {
self.clients.matchAll({includeUncontrolled: true}).then(clients => {
for (const client of clients)
client.postMessage({type: 'error', message: err.message});
});
});
});
I am using a service worker to provide caching for my site's assets (HTML, JS, CSS).
When I use Firefox my sw.js is installed correctly and the required files cached. If I go into offline mode I get the site styled correctly with everything present but the data (which is correct as the data is not being cached).
However when I use Chrome I'm getting a TypeError: Failed to fetch error. I'm really unsure why I'm getting this error since it works in Firefox. In addition I'm getting the same error thrown whenever the fetch event fires and the request if for an asset that is not in the cache (and the fetch function is being called).
If I pass an empty array to the cache.addAll function I don't get any errors until attempting to actually handling the fetch event.
It's maybe worth noting that none of the files I'm caching are all coming from localhost and not any other origin so I can't see this being a cross-domain issue.
This is the console output when installing the service worker:
This is the console output when refreshing the page after installing the service worker:
This is the code for my service worker:
const CACHE_NAME = 'webapp-v1';
const CACHE_FILES = [
'/',
'/public/app.css',
'/public/img/_sprites.png',
'/public/js/app.min.js',
'/public/js/polyfills.min.js'
];
self.addEventListener('install', event => {
console.log("[sw.js] Install event.");
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(CACHE_FILES))
.then(self.skipWaiting())
.catch(err => console.error("[sw.js] Error trying to pre-fetch cache files:", err))
);
});
self.addEventListener('activate', event => {
console.log("[sw.js] Activate event.");
event.waitUntil(
self.clients.claim()
);
});
self.addEventListener('fetch', event => {
if (!event.request.url.startsWith(self.location.origin)) return;
console.log("[sw.js] Fetch event on", event.request.url);
event.respondWith(
caches.match(event.request).then(response => {
console.info("[sw.js] Responded to ", event.request.url, "with", response ? "cache hit." : "fetch.");
return response || fetch(event.request);
}).catch(err => {
console.error("[sw.js] Error with match or fetch:", err);
})
);
});
Any help would be great.
cache.addAll(CACHE_FILES)
will fail when 1 of the file is not accessible (HTTP 400,401 etc, also 5XX and 3XX sometimes) to avoid failing all when 1 fail use individual catch statement in a map loop like here https://github.com/GrosSacASac/server-in-the-browser/blob/master/client/js/service_worker.js#L168
the fact that it does not fail with empty array probably means you have an inaccessible resource in CACHE_FILES.
Maybe firefox is less restrective and caches the body of the 400 response.
Inside your fetch handler you try to use caches.match directly but I think that is not legal. you must open the caches first and then from an opened cache you can do cache.match. See https://github.com/GrosSacASac/server-in-the-browser/blob/master/client/js/service_worker.js#L143
I have added a service worker to my page with the code below. It works well once the page has been reloaded and the worker already installed. But does not seem to catch any fetch events before the page is reloaded after I have seen the 'SW INSTALL' log.
app.js
navigator.serviceWorker.register('/worker.js').then((registration) =>
{
console.log('ServiceWorker registration successful with scope: ',
registration.scope);
}, (err) =>
{
console.log('ServiceWorker registration failed: ', err);
});
worker.js
self.addEventListener('install', function (event)
{
console.log("SW INSTALL");
});
self.addEventListener('fetch', function (event)
{
console.log("FETCHING", event);
event.respondWith(
caches.match(event.request)
.then(function (response, err)
{
// Cache hit - return response
if (response)
{
console.log("FOUND", response, err);
return response;
}
console.log("MISSED", event.request.mode);
return fetch(event.request)
}
)
);
});
Solution: Adding the following to worker.js;
self.addEventListener('activate', function (event)
{
event.waitUntil(self.clients.claim());
});
Service workers donât immediately âclaimâ the sessions that load them,
meaning that until your user refreshes the page your service worker
will be inactive.
The reason for this is consistency, given that you might otherwise end
up with half of your webpageâs assets cached and half uncached if a
service worker were to come alive partway through your webpageâs
initialization. If you donât need this safeguard, you can call
clients.claim and force your service worker to begin receiving events
Read more # service-workers