8

I'd like to intercept fetch from all parts and libraries in my application, and at the same time I'd like not to break possibility of working with the application via file URL - it is useful for Electron and mobile devices (via WebView). For now, I've found two possible ways of doing this:

  1. something like here

    const realFetch = window.fetch;
    window.fetch = function() {
    // do something
    return realFetch.apply(this, arguments)
    }
    
  2. something like here, with service worker registration:

main.js:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
  navigator.serviceWorker.register('sw.js').then(function(registration) {
    console.log('Service worker registered with scope: ', registration.scope);
  }, function(err) {
  console.log('ServiceWorker registration failed: ', err);
});
});
}

sw.js:

self.addEventListener('fetch', function(event) {
  event.respondWith(
  // intercept requests by handling event.request here
  );
});

With the first approach I cannot intercept fetch requests from web workers. The second approach doesn't work with file URLs, and I want my application to work via file URL due to it allows me to use the app via Electron for desktops or WebView for Android. Is there any other way for intercepting fetch requests?

P.S. I cannot modify the worker I'm trying to intercept requests from.

Update: On basis of @Ciro Corvino's answer, I've tried the third approach: to start my own worker before anythnig else and try to redefine fetch from there. Didn't work for me, unfortunately, here is the code:

function redefineFetch() {
console.log('inside worker');
if (self.fetch == null) {
    console.log('null!');
} else {
    console.log(self.fetch.toString());
}
const originalFetch: WindowOrWorkerGlobalScope['fetch'] = self.fetch;
self.fetch = (input: RequestInfo, init: RequestInit) => {
    console.log('overridden');
    return originalFetch(input, init);
}
}
const blob = new Blob(['(' +
redefineFetch.toString() + ')()'], {type: 'text/javascript'});

const blobUrl = window.URL.createObjectURL(blob);
const w = new Worker(blobUrl);

I'm sure that this code starts before the other workers (I've added a timeout), but this doesn't redefine fetch for the other workers. Can someone explain why or fix the solution?

Update 2: Apparently each worker has it's own private WorkerGlobalScope, otherwise there would be no sense to use messages for inter-worker communications. Probably, another fix for my problem could be in overriding Worker constructor, if this is possible. Will check it.

Anton Pilyak
  • 1,050
  • 2
  • 15
  • 34
  • Are you sure that redefineFetch gets called on new Worker instance? try to call it also out of each worker context, into window context/scope – Ciro Corvino Feb 28 '19 at 13:37
  • In the console I have 'inside worker', so, yes, it is called. and I don't have control on the other workers, they're launched by the library, a very huge one – Anton Pilyak Feb 28 '19 at 13:41
  • unfortunately you need call it in every worker you want intercept separately. The code of the worker cannot be shared with the others, youl'll get missing reference errors.. I tryed a version at https://js.do/code/290581 and i confirm that works in the worker context you have instantiated with the blob and in window scope after a call of redefineFetch in this context. The execution order seems to give priority to window context – Ciro Corvino Feb 28 '19 at 14:38

1 Answers1

2

Just try to override the fetch method of the current WorkerGlobalScope into main javascript context (window) and into each js file run in a dedicated worker context calling this function:

note that the self property returns the specialized scope for each context

 //works in each worker context you call it and enable fetch interception
 function EnableFetchWithArguments() {
   const originalCtxFetch = self.fetch;
   self.fetch = function() {
     // Get the parameter in arguments
     // Intercept the parameter here 
     return originalCtxFetch.apply(this, arguments)
   }
 }

see for reference and browser compatiblity: WorkerGlobalScope

Ciro Corvino
  • 2,038
  • 5
  • 20
  • 33
  • Sorry but your OP references to javascript (see keywords on the bottom..) also your links (see "here"...). Typescript is completely javascript compliant but to be fast I suggest to reference the js library WindowOrWorker... on the outer of the typescript context and that is in the html template, in this case simply add a script tag with the javascript.. – Ciro Corvino Feb 27 '19 at 13:58
  • Thanks for the answer, but I'm having an error saying that WindowOrWorkerGlobalScope is not defined (that's in Javascript). I'm using the latest stable Chrome, it seems like WindowOrWorkerGlobalScope.fetch is not a valid expression. Typescript also complains on that saying that WindowOrWorkerGlobalScope is just a type and not a value – Anton Pilyak Feb 27 '19 at 13:59
  • Moreover, WindowOrWorkerGlobalScope seems to be an interface, and not an implementation. But, it might be a good idea to start my own worker first and override the fetch method from there. – Anton Pilyak Feb 27 '19 at 14:12
  • Sorry again, i've just seen that WindowOrGlobal.. is a mixin not an interface and cannot be instantiated with an implementation.. just try to replace it with "self" – Ciro Corvino Feb 27 '19 at 14:16
  • Unfortunately doesn't work either - pls, see the updated question – Anton Pilyak Feb 28 '19 at 10:56