Following "Random notes around service workers development and testing", here are a few more notes and gotchas related to Service Workers, focusing on dealing with CORS and opaque responses.
Service Workers and non-CORS (opaque) responses
Service Workers intercept non-CORS responses and cache them as opaque using the Cache API. These responses are intercepted with a status code of 0
. Due to their opaque nature, JavaScript cannot inspect these responses, making it impossible for Service Workers to cache them conditionally based on status codes, headers, or other criteria.
Using opaque responses as resources on a page
Assets loaded via src
attributes on HTML elements such as img
, video
, etc., are requested as no-cors
by default. Therefore, their responses will be opaque. Opaque responses can be used as resources on a web page whenever the browser allows for non-CORS cross-origin resources. Here's a subset of elements for which non-CORS cross-origin resources, and therefore opaque responses, are valid:
<script>
<link rel="stylesheet">
<img>
,<video>
, and<audio>
<object>
and<embed>
<iframe>
You can change these to be CORS-based by adding the crossorigin
attribute.
It's important to note that opaque responses are not valid for font resources.
To determine whether you can use an opaque response as a particular type of resource on a page, check the relevant specification. For example, the HTML specification explains that non-CORS cross-origin (i.e., opaque) responses can be used for <script>
elements, though with some limitations to prevent leaking error information.
Limitations of opaque responses
- Inspection: Service Workers (or JavaScript in general) can't inspect opaque responses, including determining their size.
- Access to Headers and Body: You cannot get meaningful information from most properties of the Response class, like headers, or call methods from the
Body
interface, likejson()
ortext()
.
Rewrite no-cors
requests to inspect opaque response
As a workaround to the limitations of opaque responses, you can intercept no-cors
requests in the Service Worker and rewrite them as CORS. This allows you to inspect and conditionally cache them based on their status code.
For example, with Workbox you can do the following:
const forceCorsRequestPlugin: WorkboxPlugin = {
requestWillFetch: async ({ request }) => {
// Don't modify the request if it's not a no-cors request.
// (e.g.: because it includes credentials).
if (request.mode !== "no-cors") {
return request;
}
return new Request(request, { mode: "cors", credentials: "omit" });
},
};
const forceCorsRequestPlugin: WorkboxPlugin = {
requestWillFetch: async ({ request }) => {
// Don't modify the request if it's not a no-cors request.
// (e.g.: because it includes credentials).
if (request.mode !== "no-cors") {
return request;
}
return new Request(request, { mode: "cors", credentials: "omit" });
},
};
Opaque Responses & the Cache Storage API
A small detail around the behavior of the Cache Storage API (the API used by Service Workers to handle caching): when using opaque responses with the Cache Storage API, its add()
and addAll()
methods reject responses with status codes outside the 2XX range.
Consequently, opaque responses will fail to be added to the cache using add()
or addAll()
. To work around this, explicitly perform a fetch()
and then call the put()
method with the opaque response.
Opaque Responses & the navigator.storage
API
To prevent cross-domain information leakage, browsers add significant padding to the size of opaque responses when calculating storage quota limits. In Google Chrome, the minimum size contribution of a single cached opaque response to overall storage usage is approximately 7 megabytes. Keep this in mind when determining how many opaque responses to cache, as you may exceed storage quota limitations sooner than expected.
Workbox caching strategies and opaque responses
Workbox suggests (and defaults to) caching only status code 200
for cache-first strategies. This means opaque responses won't be cached in this scenario. However, stale-while-revalidate strategies do cache both status codes 200
and 0
.