0

I'm trying to monkey patch window.fetch inside a class to be able to fetch request and response body.

I currently have the following implementation:

export class NetworkService implements Listener {
  originalFetch: typeof window.fetch

  constructor() {
    this.originalFetch = window.fetch
  }

  async listenFetch(): Promise<void> {
    if (typeof window.fetch !== 'function') return

    window.fetch = async (input: RequestInfo, init?: RequestInit) => {
      const response = await this.originalFetch(input, init);

      try {
        response
          .clone()
          .text()
          .then(body => console.log(body))
          .catch(err => console.error(err));
      } catch (e) {
        console.log('Error reading response: ' + e)
      }

      return response;
    };
  }


  registerListeners(): void {
    this.listenFetch()
  }

  unregisterListeners(): void {
    if (typeof fetch !== 'function') return

    window.fetch = this.originalFetch
  }
}

But whenever I make a simple fetch request from my page I get Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation.

The following question Fetch TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation and tried

constructor(
    fetch: FetchFunction = (...args) => window.fetch(...args)
  ) {
    this.originalFetch = fetch;
  }

But this leads to a never ending recursion.

How can I monkey patch window.fetch in a class? More importantly, why am I getting Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation?

Edit

Thank you for the replies. I have transformed my arrow function into an a regular one:

async listenFetch(): Promise<void> {
    if (typeof fetch !== 'function') return

    const self = this

    window.fetch = async function (input: RequestInfo, init?: RequestInit)  {
      const response = await self.originalFetch(input, init);

      try {
        response
          .clone()
          .text()
          .then(body => console.log(body))
          .catch(err => console.error(err));
      } catch (e) {
        console.log('Error reading response: ' + e)
      }

      return response;
    };
  }

I'm still getting Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation

Bruno Francisco
  • 3,841
  • 4
  • 31
  • 61
  • 1
    You are using an arrow function which changes this – Daniel A. White Jun 26 '23 at 16:58
  • Does this answer your question? [Methods in ES6 objects: using arrow functions](https://stackoverflow.com/questions/31095710/methods-in-es6-objects-using-arrow-functions) – tevemadar Jun 26 '23 at 17:00
  • @DanielA.White I have updated the question to include the changes that you have suggested (use regular function instead of arrow one). I'm still getting `Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation` – Bruno Francisco Jun 26 '23 at 17:04
  • @tevemadar unfortunately no. I have updated my original question with an edit to showcase that after replacing the arrow function with a regular one, I still face the same issue – Bruno Francisco Jun 26 '23 at 17:10
  • @DanielA.White would you mind taking a look at the answer I left for the question? I would like to understand if this is the reason why I was able to solve the issue the way I did – Bruno Francisco Jun 26 '23 at 17:27

1 Answers1

0

Thanks to Daniel White and tevemadar comments I was able to make a working version:

async listenFetch(): Promise<void> {
    if (typeof fetch !== 'function') return

    const originalFetch = window.fetch

    window.fetch = async function (...args) {
      const response = await originalFetch(...args)

      return response
    }
  }

I was storing the original fetch as a property in the class, while now I'm storing the original fetch inside a variable before using it.

By this article, this is what an "Illegal Invocation" means:

An "illegal invocation" error is thrown when calling a function whose this keyword doesn't refer to the object where it originally did. In other words, the original "context" of the function is lost. Chromium browsers call this error an "illegal invocation.

My best assumption here is that when I store the original fetch in the class's property the context is different than when I will use it, therefore losing the initial context.

class NetworkService {
  originalFetch: typeof window.fetch

  constructor() {
   // "originalFetch" was registered here in this context
    this.originalFetch = window.fetch
  }

  listenFetch() {
    window.fetch = async function (...args) {
      // `this` is a different context now than when we stored the original fetch
      const response = this.originalFetch(...args)
    }
  }
}
Bruno Francisco
  • 3,841
  • 4
  • 31
  • 61
  • 1
    You can also use `this.originalFetch.call(window, ...args)` (or `.apply(window, args)`) – Bergi Jun 26 '23 at 20:06