4

How can I get the full URL of the page which is being serviced by a service worker's 'fetch' event?

The "self.location" property seems to only refer to the root URL of the site. For example, if page https://example.com/folder/pagename.html is performing a fetch which the service worker is intercepting, the service worker's 'self.location' property returns "https://example.com".

event.currentTarget.location and event.explicitOriginalTarget.location, event.originalTarget, and event.target all return the URL of the service worker .js file.

How can I get the full URL of the page that triggered the fetch event?

Ryan Griggs
  • 2,457
  • 2
  • 35
  • 58

1 Answers1

8

You've got two general approaches, depending on how involved you want to get:

Use the 'Referer' header info

If the request is for a subresource and includes a Referer header, then there's a decent chance that the value of that header is the URL of the page that made the request. (There are some caveats; read this background info to delve into that.)

From within a fetch handler, you can read the value of that header with the following:

self.addEventListener('fetch', event => {
  const clientUrl = event.request.referrer;
  if (clientUrl) {
    // Do something...
  } 
});

Use the clientId value

Another approach is to use the clientId value that (might) be exposed on the FetchEvent, and then use clients.get(id) or loop through the output of clients.matchAll() to find the matching WindowClient. You could then read the url property of that WindowClient.

One caveat with this approach is that the methods which look up the WindowClient are all asynchronous, and return promises, so if you're somehow using the URL of the client window to determine whether or not you want to call event.respondWith(), you're out of luck (that decision needs to be made synchronously, when the FetchEvent handler is first invoked).

There's a combination of different things that need to be supported in order for this approach to work, and I'm not sure which browsers currently support everything I mentioned. I know Chrome 67 does, for instance (because I just tested it there), but you should check in other browsers if this functionality is important to you.

self.addEventListener('fetch', async event => {
  const clientId = event.clientId;
  if (clientId) {
    if ('get' in clients) {
      const client = await clients.get(clientId);
      const clientUrl = client.url;
      // Do something...
    } else {
      const allClients = await clients.matchAll({type: 'window'});
      const filtered = allClients.filter(client => client.id === clientId);
      if (filtered.length > 0) {
        const clientUrl = filtered[0].url;
        // Do something...
      }
    }
  }
});
Jeff Posnick
  • 53,580
  • 14
  • 141
  • 167
  • Your answer referencing event.request.headers *almost* worked. However, event.request.headers.get('referer') returns 'null'.(So does get('referrer').) Instead, referencing event.request.referrer (spelling with two 'r's) actually returns the URL of the page requesting the fetch. Thanks for getting me pointed in the right direction. – Ryan Griggs Apr 27 '18 at 15:12
  • Hello from 2020-04-19 where only `event.request.headers.get( 'referer' )` is working, at least for me – IT-Dan Apr 19 '20 at 09:14
  • Hmmm... it's defined as a property on the `Request`: https://developer.mozilla.org/en-US/docs/Web/API/Request/referrer – Jeff Posnick Apr 20 '20 at 13:52
  • As of the time of this writing, you CAN use respondWith with async code. This was probably changed since the original answer was posted here, so here is some example code: https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith#examples – tkd_aj Apr 21 '22 at 20:49
  • My point was that the _decision_ as to whether you call `event.respondWith()` needs to be based on synchronous criteria (i.e. something that does not involve promises). After you've made that decision, you pass a promise as a parameter `event.respondWith()`, and that promise can be the return value of an `async` function. – Jeff Posnick Apr 25 '22 at 12:30
  • 1
    Ah, I see your point. I did do something like this, but in my case I had some synchronous decisions which had to be made AND some async ones. So, what I did was something like: https://gist.github.com/tkdaj/9aa196792ba8aac3279ef9e9af3f54ba This may cause your requests to slow down and be unacceptable. In my case it was used for adding a debugging tool to my app, so it was okay with me. – tkd_aj May 03 '22 at 21:11