Source: index.js

/**
* The FetchInterceptor class
*/
class FetchInterceptor {
  /**
  * Recognize global environment and attach fetch
  */
  constructor() {
    const ENVIRONMENT_IS_REACT_NATIVE
      = typeof navigator === 'object' && navigator.product === 'ReactNative';
    const ENVIRONMENT_IS_NODE
      = typeof process === 'object' && typeof require === 'function';
    const ENVIRONMENT_IS_WEB = typeof window === 'object';
    const ENVIRONMENT_IS_WORKER = typeof importScripts === 'function';

    if (ENVIRONMENT_IS_REACT_NATIVE) {
      this.env = global;
    } else if (ENVIRONMENT_IS_WORKER) {
      this.env = self;
    } else if (ENVIRONMENT_IS_WEB) {
      this.env = window;
    } else if (ENVIRONMENT_IS_NODE) {
      this.env = global;
    } else {
      throw new Error('Unsupported environment for fetch-intercept');
    }

    this.fetch = this.env.fetch;
  }

  /**
  * Whitelist hooks
  */
  static hooks = [
    'onBeforeRequest',
    'onRequestSuccess',
    'onRequestFailure',
  ];

  /**
  * Register intercept hooks & return an interceptor instance
  * @param {object} hooks - The intercept hooks
  * @return {FetchInterceptor} An interceptor object
  */
  static register(hooks = {}) {
    if (this._instance) {
      return this._instance;
    }
    const interceptor = new this();
    for (let i = 0; i < this.hooks.length; i++) {
      const hook = this.hooks[i];
      if (typeof hooks[hook] === 'function') {
        interceptor[hook] = hooks[hook];
      }
    }
    interceptor.hijack();
    this._instance = interceptor;
    return interceptor;
  }

  /**
  * Reset fetch and unregister intercept hooks
  */
  unregister() {
    this.env.fetch = this.fetch;
    delete this.constructor._instance;
  }

  /**
  * Hijack global fetch and insert registered hooks if present
  */
  hijack() {
    const controller = new AbortController();
    const signal = controller.signal;
    this.env.fetch = (...a) => {
      let request;
      if (a[0] instanceof Request) {
        let object = {};
        [
          'cache',
          'context',
          'credentials',
          'destination',
          'headers',
          'integrity',
          'method',
          'mode',
          'redirect',
          'referrer',
          'referrerPolicy',
          'url',
          'body',
          'bodyUsed',
        ].forEach((prop) => {
          if (prop in a[0]) {
            object[prop] = a[0][prop];
          }
        });
        object.signal = signal;
        const {
          url,
          ...options
        } = object;
        request = new Request(url, options);
      } else {
        const url = a[0];
        const options = {
          ...a[1],
          signal,
        };
        request = new Request(url, options);
      }
      if (typeof this.onBeforeRequest === 'function') {
        this.onBeforeRequest(request, controller);
      }
      const promise = this.fetch.call(this.env, request);
      if (typeof this.onAfterRequest === 'function') {
        this.onAfterRequest(request, controller);
      }
      return promise.then((response) => {
        if (response.ok) {
          if (typeof this.onRequestSuccess === 'function') {
            this.onRequestSuccess(response, request, controller);
          }
        } else {
          if (typeof this.onRequestFailure === 'function') {
            this.onRequestFailure(response, request, controller);
          }
        }
        return response;
      });
    };
  }
}

module.exports = FetchInterceptor;