Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service Worker: Append header to requests for CSS & JS files

I've been trying to use service workers for (what seems like hours & hours), to attach a simple header to all requests. Whats frustrating is, it sort of works.

Attempt 1:

self.addEventListener("fetch", event => {
   const modifiedHeaders = new Headers({
      ...event.request.headers,
      'API-Key': '000000000000000000001'
   });
   const modifiedRequest = new Request(event.request, {
      headers: modifiedHeaders,
   }); 
   event.respondWith((async () => {
     return fetch(modifiedRequest);
   })());
});

The above code works for HTML files however for CSS & JS files I get the follow error

ReferenceError: headers is not defined

If I disable the header requirement the page loads with images and javascript and I can interact with it like normal.

Attempt 2:

var req = new Request(event.request.url, {
   headers: {
     ...event.request.headers,
      'API-Key': '000000000000000000001'
      },
   method: event.request.method,
   mode:  event.request.mode,
   credentials: event.request.credentials,
   redirect: event.request.redirect,
   referrer: event.request.referrer,
   referrerPolicy: event.request.referrerPolicy,
   bodyUsed: event.request.bodyUsed,
   cache: event.request.cache,
   destination: event.request.destination,
   integrity: event.request.integrity,
   isHistoryNavigation: event.request.isHistoryNavigation,
   keepalive:  event.request.keepalive
 });

This attempt, I simply built a new request, which successfully included the new header on CSS & JS file requests. However, When I do a POST or redirect, things stop working and behave strange.

What is the correct approach for this? I feel that attempt 1 is the better path, but I can't seem to create the Headers object on the request no matter what I do.

The version of chrome I am using is

Version 78.0.3904.70 (Official Build) (64-bit)

The site is an internal developer tool so cross browser compatibility isn't required. So I'm happy to load any additional libs / enable experimental features etc.

like image 936
Jamie Avatar asked Oct 31 '19 10:10

Jamie


1 Answers

The problem is that your modified requests reuse the mode of the original request in both of your attempts

For embedded resources where the request is initiated from markup (unless the crossorigin attribute is present) the request is in most cases made using the no-cors mode which only allows a very limited specific set of simple headers.

no-cors — Prevents the method from being anything other than HEAD, GET or POST, and the headers from being anything other than simple headers. If any ServiceWorkers intercept these requests, they may not add or override any headers except for those that are simple headers...

Source and more info on request modes: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode

Simple headers are the following ones: accept (only some values), accept-language, content-language (only some values), content-type.

Source: https://fetch.spec.whatwg.org/#simple-header:

Solution:

You need to make sure to set the mode to something other than no-cors when creating the modified request. You can pick either cors or same-origin, depending on whether you want to allow cross-origin requests. (The navigate mode is reserved for navigation only and it is not possible to create a request with that mode.)

Why your code worked for HTML files:

The request issued when navigating to a new page uses the navigate mode. Chrome does not allow creating requests with this mode using the new Request() constructor, but seems to automatically silently use the same-origin mode when an existing request with the navigate mode is passed to the constructor as a parameter. This means that your first (HTML load) modified request had same-origin mode, while the CSS and JS load requests had the no-cors mode.


Working example:

'use strict';

/* Auxiliary function to log info about requests to the console */
function logRequest(message, req) {
  const headersString = [...req.headers].reduce((outputString, val) => `${outputString}\n${val[0]}: ${val[1]}`, 'Headers:');
  console.log(`${message}\nRequest: ${req.url}\nMode: ${req.mode}\n${headersString}\n\n`);
}


self.addEventListener('fetch', (event) => {
  logRequest('Fetch event detected', event.request);

  const modifiedHeaders = new Headers(event.request.headers);
  modifiedHeaders.append('API-Key', '000000000000000000001');

  const modifiedRequestInit = { headers: modifiedHeaders, mode: 'same-origin' };
  const modifiedRequest = new Request(event.request, modifiedRequestInit);

  logRequest('Modified request', modifiedRequest);

  event.respondWith((async () => fetch(modifiedRequest))());
});
like image 159
Petr Srníček Avatar answered Nov 14 '22 21:11

Petr Srníček