Is it possible to manually add to a service worker cache? - dart

I am trying to make an interface that works with service worker but it currently expects to be able to write to the cache itself.
If it is not possible, it would be helpful to know if an expired cache resource will be used when network is unavailable.
Edit specifically Response public constructors don't allow specifying the body data, and Cache.put() requires a Response.

I am trying to make an interface that works with service worker but it currently expects to be able to write to the cache itself.
The Cache Storage API is useful inside of a service worker, but it isn't limited to just the ServiceWorkerGlobalScope. It's also exposed on normal web pages as window.caches. So you can read from and write to the same caches from either your web page or your service worker (or a web worker, for that matter).
If it is not possible, it would be helpful to know if an expired cache resource will be used when network is unavailable.
There's no concept of "expired cache resource" within the Cache Storage API. The Cache-Control headers in the cached Response are effectively ignored, if that's what you're asking about. So as long as a Response is stored in a cache, you can read it and make use of it. But nothing will be used automatically—you need to specifically set up a service worker's fetch event handler and add in logic that determines what happens in various scenarios. A good, generic overview of how to write that service worker code can be found in "The Offline Cookbook".
For instance, here's a fetch handler that will respond to all network requests by first going against the network (via fetch()) and if that rejects, tries to respond with a cached response for the same URL:
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request);
})
);
});

Related

New build deployed in a domain (https://example.com) is not getting reflected as the previous build has a service worker running [duplicate]

I'm playing with the service worker API in my computer so I can grasp how can I benefit from it in my real world apps.
I came across a weird situation where I registered a service worker which intercepts fetch event so it can check its cache for requested content before sending a request to the origin.
The problem is that this code has an error which prevented the function from making the request, so my page is left blank; nothing happens.
As the service worker has been registered, the second time I load the page it intercepts the very first request (the one which loads the HTML). Because I have this bug, that fetch event fails, it never requests the HTML and all I see its a blank page.
In this situation, the only way I know to remove the bad service worker script is through chrome://serviceworker-internals/ console.
If this error gets to a live website, which is the best way to solve it?
Thanks!
I wanted to expand on some of the other answers here, and approach this from the point of view of "what strategies can I use when rolling out a service worker to production to ensure that I can make any needed changes"? Those changes might include fixing any minor bugs that you discover in production, or it might (but hopefully doesn't) include neutralizing the service worker due to an insurmountable bug—a so called "kill switch".
For the purposes of this answer, let's assume you call
navigator.serviceWorker.register('service-worker.js');
on your pages, meaning your service worker JavaScript resource is service-worker.js. (See below if you're not sure the exact service worker URL that was used—perhaps because you added a hash or versioning info to the service worker script.)
The question boils down to how you go about resolving the initial issue in your service-worker.js code. If it's a small bug fix, then you can obviously just make the change and redeploy your service-worker.js to your hosting environment. If there's no obvious bug fix, and you don't want to leave your users running the buggy service worker code while you take the time to work out a solution, it's a good idea to keep a simple, no-op service-worker.js handy, like the following:
// A simple, no-op service worker that takes immediate control.
self.addEventListener('install', () => {
// Skip over the "waiting" lifecycle state, to ensure that our
// new service worker is activated immediately, even if there's
// another tab open controlled by our older service worker code.
self.skipWaiting();
});
/*
self.addEventListener('activate', () => {
// Optional: Get a list of all the current open windows/tabs under
// our service worker's control, and force them to reload.
// This can "unbreak" any open windows/tabs as soon as the new
// service worker activates, rather than users having to manually reload.
self.clients.matchAll({type: 'window'}).then(windowClients => {
windowClients.forEach(windowClient => {
windowClient.navigate(windowClient.url);
});
});
});
*/
That should be all your no-op service-worker.js needs to contain. Because there's no fetch handler registered, all navigation and resource requests from controlled pages will end up going directly against the network, effectively giving you the same behavior you'd get without if there were no service worker at all.
Additional steps
It's possible to go further, and forcibly delete everything stored using the Cache Storage API, or to explicitly unregister the service worker entirely. For most common cases, that's probably going to be overkill, and following the above recommendations should be sufficient to get you in a state where your current users get the expected behavior, and you're ready to redeploy updates once you've fixed your bugs. There is some degree of overhead involved with starting up even a no-op service worker, so you can go the route of unregistering the service worker if you have no plans to redeploy meaningful service worker code.
If you're already in a situation in which you're serving service-worker.js with HTTP caching directives giving it a lifetime that's longer than your users can wait for, keep in mind that a Shift + Reload on desktop browsers will force the page to reload outside of service worker control. Not every user will know how to do this, and it's not possible on mobile devices, though. So don't rely on Shift + Reload as a viable rollback plan.
What if you don't know the service worker URL?
The information above assumes that you know what the service worker URL is—service-worker.js, sw.js, or something else that's effectively constant. But what if you included some sort of versioning or hash information in your service worker script, like service-worker.abcd1234.js?
First of all, try to avoid this in the future—it's against best practices. But if you've already deployed a number of versioned service worker URLs already and you need to disable things for all users, regardless of which URL they might have registered, there is a way out.
Every time a browser makes a request for a service worker script, regardless of whether it's an initial registration or an update check, it will set an HTTP request header called Service-Worker:.
Assuming you have full control over your backend HTTP server, you can check incoming requests for the presence of this Service-Worker: header, and always respond with your no-op service worker script response, regardless of what the request URL is.
The specifics of configuring your web server to do this will vary from server to server.
The Clear-Site-Data: response header
A final note: some browsers will automatically clear out specific data and potentially unregister service workers when a special HTTP response header is returned as part of any response: Clear-Site-Data:.
Setting this header can be helpful when trying to recover from a bad service worker deployment, and kill-switch scenarios are included in the feature's specification as an example use case.
It's important to check the browser support story for Clear-Site-Data: before your rely solely on it as a kill-switch. As of July 2019, it's not supported in 100% of the browsers that support service workers, so at the moment, it's safest to use Clear-Site-Data: along with the techniques mentioned above if you're concerned about recovering from a faulty service worker in all browsers.
You can 'unregister' the service worker using javascript.
Here is an example:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
//returns installed service workers
if (registrations.length) {
for(let registration of registrations) {
registration.unregister();
}
}
});
}
That's a really nasty situation, that hopefully won't happen to you in production.
In that case, if you don't want to go through the developer tools of the different browsers, chrome://serviceworker-internals/ for blink based browsers, or about:serviceworkers (about:debugging#workers in the future) in Firefox, there are two things that come to my mind:
Use the serviceworker update mechanism. Your user agent will check if there is any change on the worker registered, will fetch it and will go through the activate phase again. So potentially you can change the serviceworker script, fix (purge caches, etc) any weird situation and continue working. The only downside is you will need to wait until the browser updates the worker that could be 1 day.
Add some kind of kill switch to your worker. Having a special url where you can point users to visit that can restore the status of your caches, etc.
I'm not sure if clearing your browser data will remove the worker, so that could be another option.
I haven't tested this, but there is an unregister() and an update() method on the ServiceWorkerRegistration object. you can get this from the navigator.serviceWorker.
navigator.serviceWorker.getRegistration('/').then(function(registration) {
registration.update();
});
update should then immediately check if there is a new serviceworker and if so install it. This bypasses the 24 hour waiting period and will download the serviceworker.js every time this javascript is encountered.
For live situations you need to alter the service worker at byte-level (put a comment on the first line, for instance) and it will be updated in the next 24 hours. You can emulate this with the chrome://serviceworker-internals/ in Chrome by clicking on Update button.
This should work even for situations when the service worker itself got cached as the step 9 of the update algorithm set a flag to bypass the service worker.
We had moved a site from godaddy.com to a regular WordPress install. Client (not us) had a serviceworker file (sw.js) cached into all their browsers which completely messed things up. Our site, a normal WordPress site, has no service workers.
It's like a virus, in that it's on every page, it does not come from our server and there is no way to get rid of it easily.
We made a new empty file called sw.js on the root of the server, then added the following to every page on the site.
<script>
if (navigator && navigator.serviceWorker && navigator.serviceWorker.getRegistration) {
navigator.serviceWorker.getRegistration('/').then(function(registration) {
if (registration) {
registration.update();
registration.unregister();
}
});
}
</script>
In case it helps someone else, I was trying to kill off service workers that were running in browsers that had hit a production site that used to register them.
I solved it by publishing a service-worker.js that contained just this:
self.globalThis.registration.unregister();

Completely replacing a request in a WebExtension

I'm looking to make a web extension for Firefox that stores HTML pages and other resources in local storage and serves them for offline viewing. To do that, I need to intercept requests that the browser makes for the pages and the content in them.
Problem is, I can't figure out how to do that. I've tried several approaches:
The webRequest API doesn't allow fulfilling a request entirely - it can only block or redirect a request, or edit the response after it's been done.
Service Workers can listen to the fetch event, which can do what I want, but calling navigator.serviceWorker.register in an addon page (the moz-extension://<id> domain) results in an error: DOMException: The operation is insecure. Relevant Firefox bug
I could possibly set up the service worker on a self hosted domain with a content script, but then it won't be completely offline.
Is there an API that I missed that can intercept requests from inside a web extension?

Is service-worker with CacheAPI or IndexedDB viable for downloading large datasets

I have a following situation: I am developing a small web application with the following properties:
must be available offline (it is intended for outdoor use in areas with poor cell reception)
when online, each user "produces" his/her own offline content which is placed inside a publicly available folder for each user on the server. This content (sets of images, up to 40Mb per user, to be concrete) is necessary for the app to have any usefulness
First point is fine, service workers are just the thing i need as they will download and cache my application logic, but i am struggling to figure out, if i should or even can use service workers for the last requirement. As i understand, service workers have the following property: the structure of a service worker (what is included) is determined at application build step and cannot be changed or modified at runtime, and even if it was, i could not generate different service-workers for different users, correct?
Is there another approach for this?
You can use the Cache Storage API to cache additional content for users, beyond your web app's HTML/JS/CSS. The Cache Storage API is exposed to both client web pages and inside of a service worker as caches on the global object.
You can, for instance, make the following call within your web app's code:
async function cacheAdditionalUrls(urls) {
const cache = await caches.open('my-custom-cache');
await cache.addAll(url);
// At this point, 'my-custom-cache' will contain all of the urls.
// You can read from this cache from the service worker.
}
And in your service worker, you can read from 'my-custom-cache' to fulfill matching requests. You mention that you're using vue-cli's PWA plugin, which is built on top of Workbox. To use that cached data in Workbox, you can implement a runtimeCaching rule in the Workbox configuration, like so:
pwa: {
workboxOptions: {
// Add in other options as needed.
runtimeCaching: [{
urlPattern: new RegExp('path/prefix/for/images'),
handler: 'cacheFirst',
options: {
cacheName: 'my-custom-cache'
}
}]
}
}

Is there a way to create connection timeout to activate a service-worker?

I'm using Electron, which is based on Chromium, to create an offline desktop application.
The application uses a remote site, and we are using a service worker to offline parts of the site. Everything is working great, except for a certain situation that I call the "airplane wifi situation".
Using Charles, I have restricted download bandwidth to 100bytes/s. The connection is sent through webview.loadURL which eventually calls LoadURLWithParams in Chromium. The problem is that it does not fail and then activate the service worker, like no connection at all would. Once the request is sent, it waits forever for the response.
My question is, how do I timeout the request after a certain amount of time and load everything from the service worker as if the user was truly offline?
An alternative to writing this yourself is to use the sw-toolbox library, which provides routing and runtime caching strategies for service workers, along with some built in options for helping with these sorts of advanced use cases. In particular, you'd want to use the networkTimeoutSeconds parameter to configure the amount of time to wait for a response from the network before you fall back to a previously cached response.
You can use it like the following:
toolbox.router.get(
new RegExp('my-api\\.com'),
toolbox.networkFirst, {
networkTimeoutSeconds: 10
}
);
That would configure a route that matched GET requests with URLs containing my-api.com, and applied a network-first strategy that will automatically fall back to the previously cached response after 10 seconds.

Can I have multiple service workers both intercept the same fetch request?

I'm writing a library which I will provide for 3rd parties to run a service worker on their site. It needs to intercept all network requests but I want to allow them to build their own service worker if they like.
Can I have both service workers intercept the same fetches and provide some kind of priority/ordering between them? Alternatively is there some other pattern I should be using?
Thanks
No, you can not. Only one service worker per scope is allowed to be registered so the latest kick the previous one out unless the scope is more specific, in this case, the request is attended by the most specific only.
Nevertheless, you can attach multiple fetch handlers and they all will process the request so maybe you can write your functionality in a separated script and let the user's service worker to include your file via importScripts().
The first handler calling event.respondWith() synchronously (actually, you can not call this method asynchronously) wins and the remaining handlers trying to call will throw.
Prioritization and coordination requires middleware. You can check ServiceWorkerWare or sw-toolbox.

Resources