Search code examples
javascriptgoogle-chrome-extensionxmlhttprequestfetch-apifirefox-addon-webextensions

webRequest API: How to get the requestId of a new request?


The chrome.webRequest API has the concept of a request ID (source: Chrome webRequest documention):

Request IDs

Each request is identified by a request ID. This ID is unique within a browser session and the context of an extension. It remains constant during the the life cycle of a request and can be used to match events for the same request. Note that several HTTP requests are mapped to one web request in case of HTTP redirection or HTTP authentication.

You can use it to correlate the requests even across redirects. But how do you initially get hold off the id when start a new request with fetch or XMLHttpRequest?

So far, I have not found anything better than to use the URL of the request as a way to make the initial link between the new request and the requestId. However, if there are overlapping requests to the same resource, this is not reliable.

Questions:

  • If you make a new request (either with fetch or XMLHttpRequest), how do you reliably get access to the requestId?
  • Does the fetch API or XMLHttpRequest API allow access to the requestId?

What I want to do is to use the functionality provided by the webRequest API to modify a single request, but I want to make sure that I do not accidentally modify other pending requests.


Solution

  • To the best of my knowledge, there is no direct support in the fetch or XHMLHttpRequest API. Also I'm not aware of completely reliable way to get hold of the requestId.

    What I ended up doing was installing a onBeforeRequest listener, storing the requestId, and then immediately removing the listener again. For instance, it could look like this:

    function makeSomeRequest(url) {
    
      let listener;
      const removeListener = () => {
        if (listener) {
          chrome.webRequest.onBeforeRequest.removeListener(listener);
          listener = null;
        }
      };
    
      let requestId;
      listener = (details) => {
        if (!requestId && urlMatches(details.url, url)) {
          requestId = details.requestId;
          removeListener();
        }
      };
      chrome.webRequest.onBeforeRequest.addListener(listener, { urls: ['<all_urls>'] });
    
      // install other listeners, which can then use the stored "requestId"
      // ...
    
      // finally, start the actual request, for instance
      const promise = fetch(url).then(doSomething);
    
      // and make sure to always clean up the listener
      promise.then(removeListener, removeLister);
    }
    

    It is not perfect, and matching the URL is a detail that I left open. You could simply compare whether the details.url is identical to url:

    function urlMatches(url1, url2) {
      return url1 === url2;
    }
    

    Note that it is not guaranteed that you see the identical URL, for instance, if make a request against http://some.domain.test, you will see http://some.domain.test/ in your listener (see my other question about the details). Or http:// could have been replaced by https:// (here I'm not sure, but it could be because of other extensions like HTTPS Everywhere).

    That is why the code above should only be seen as a sketch of the idea. It seems to work good enough in practice, as long as you do not start multiple requests to the identical URL. Still, I would be interested in learning about a better way to approach the problem.