index.js

let originOpen = XMLHttpRequest.prototype.open;
let originSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
let originSend = XMLHttpRequest.prototype.send;

let symbols = {
  method: Symbol('method'),
  url: Symbol('url'),
  isAsync: Symbol('isAsync'),
  body: Symbol('body'),
  headers: Symbol('headers'),
  fn: {
    openInterceptor: Symbol('openInterceptor'),
    shouldOpen: Symbol('itShouldOpen'),
    shouldSend: Symbol('itShouldSend'),
    ShouldAbort: Symbol('itShouldAbort'),
    transformHeaders: Symbol('transformHeaders'),
    transformRequestBody: Symbol('transformRequestBody')
  }
};

/**
 * handler the callback.
 * @callback itShouldCallback
 * @param method  {string}    request method
 * @param url     {string}    request url
 * @param body    {string}    request body
 * @param headers {Object}    request headers
 * @returns {Boolean}
 */

/**
 * handler the callback.
 * @callback headerTransformCallback
 * @param method  {string}    request method
 * @param url     {string}    request url
 * @param body    {string}    request body
 * @param headers {Object}    request headers
 * @returns {Object}
 */

/**
 * handler the callback.
 * @callback bodyTransformCallback
 * @param method  {string}    request method
 * @param url     {string}    request url
 * @param body    {string}    request body
 * @param headers {Object}    request headers
 * @param [response]  {any}   response
 * @returns {String}
 */

/**
 * handler the callback.
 * @callback openInterceptorCallback
 * @param method      {string}    request method
 * @param url         {string}    request url
 * @param isAsync     {Boolean}   async or not
 * @returns {Arguments}
 */

/**
 * handler the callback.
 * @callback itShouldOpenCallback
 * @param method      {string}    request method
 * @param url         {string}    request url
 * @param isAsync     {Boolean}   async or not
 * @returns {Boolean}
 */

/**
 * @namespace ajah
 * @property env {String} the Javascript run-time, it should be 'dev' or 'prod'
 * @example
 ajah
 .openInterceptor(function (method, url, isAsync) {
    method = method === 'DELETE' ? 'GET' : method;    // change the http method
    return [method, url, isAsync];
  })
 .itShouldOpen(function (method, url, isAsync) {
    if (method === 'DELETE') return false;            // if the method is DELETE, the it will not hand shake with the serve
    return true;
  })
 .itShouldSend(function (method, url, body, headers) {
    if (!headers.Authorization) return false;         // if this request without token field, so don't send thi request
    return true;
  })
 .transformHeaders(function (method, url, body, headers) {
    headers['Authorization'] = 'this is a mock token';    // add a headers field for this request
    return headers;
  })
 .transformRequestBody(function (method, url, body, headers) {
    if (method === 'GET') return null;                    // if method is GET, then it should not send any body.
    return body;
  })
 .transformResponseBody(function (method, url, body, headers, response) {
    // TODO
    return response;
  })
 .itShouldAbort(function (method, url, body, headers) {
    if (url.indexOf('https://') < 0) return true;         // if this request is not https, then abort this request.
    return false;
  });
 */
const ajah = {
  env: 'prod',
  /**
   * hook before call .open() method, and you can change the arguments
   * @param callback {openInterceptorCallback}
   * @returns {ajah}
   */
  openInterceptor(callback){
    ajah[symbols.fn.openInterceptor] = callback;
    return this;
  },
  /**
   * it should be hand-shake with serve or not
   * @param callback  {itShouldOpenCallback}
   * @returns {ajah}
   */
  itShouldOpen(callback){
    ajah[symbols.fn.shouldOpen] = callback;
    return this;
  },
  /**
   * this request should be send or not
   * @param callback {itShouldCallback}
   * @returns {ajah}
   */
  itShouldSend(callback){
    ajah[symbols.fn.shouldSend] = callback;
    return this;
  },
  /**
   * after the request was sent, this request should abort or not
   * @param callback  {itShouldCallback}
   * @returns {ajah}
   */
  itShouldAbort(callback){
    ajah[symbols.fn.itShouldAbort] = callback;
    return this;
  },
  /**
   * transform the request headers
   * @param callback {headerTransformCallback}
   * @returns {ajah}
   */
  transformHeaders(callback) {
    ajah[symbols.fn.transformHeaders] = callback;
    return this;
  },
  /**
   * transform the request body
   * @param callback {bodyTransformCallback}
   * @returns {ajah}
   */
  transformRequestBody(callback){
    ajah[symbols.fn.transformRequestBody] = callback;
    return this;
  },
  /**
   * transform the response body
   * @param callback {bodyTransformCallback}
   * @returns {ajah}
   */
  transformResponseBody(callback){
    ajah[symbols.fn.transformResponseBody] = callback;
    return this;
  },
  [symbols.fn.shouldSend]: function (method, url, body, headers) {
    return true;
  },
  [symbols.fn.openInterceptor]: function (method, url, isAsync) {
    return arguments;
  },
  [symbols.fn.itShouldAbort]: function (method, url, body, headers) {
    return false;
  },
  [symbols.fn.transformHeaders]: function (method, url, body, headers) {
    return headers;
  },
  [symbols.fn.transformRequestBody]: function (method, url, body, headers) {
    return body;
  },
  [symbols.fn.transformResponseBody]: function (method, url, body, headers, response) {
    return response;
  }
};

XMLHttpRequest.prototype.open = function () {
  let method = arguments[0];
  let url = arguments[1];
  let isAsync = arguments[2];

  if (ajah.env === 'dev') {
    console.log(`[${method}]: ${url}`);
  } else {

  }

  let openInterceptor = ajah[symbols.fn.openInterceptor];

  let [_method,_url,_isAsync] = (openInterceptor.call(this, method, url, isAsync) || []);

  this[symbols.method] = _method || method;
  this[symbols.url] = _url || url;
  this[symbols.isAsync] = _isAsync || isAsync;

  let itShouldOpenFn = ajah[symbols.fn.shouldOpen];

  let params = [this[symbols.method], this[symbols.url], this[symbols.isAsync]];

  try {
    let itShouldOpen = itShouldOpenFn.apply(this, params);
    if (itShouldOpen === false) throw new Error('it should not open the serve');

    originOpen.apply(this, params);
  } catch (err) {
    console.error(err);
  }

};

XMLHttpRequest.prototype.setRequestHeader = function () {
  let key = arguments[0];
  let value = arguments[1];
  if (!this[symbols.headers]) this[symbols.headers] = {};
  this[symbols.headers][key] = value;
  originSetRequestHeader.apply(this, arguments)
};

XMLHttpRequest.prototype.send = function () {

  this[symbols.body] = arguments[0];

  let params = [this[symbols.method], this[symbols.url], this[symbols.body], this[symbols.headers]];

  try {
    let itShouldSendFn = ajah[symbols.fn.shouldSend];                       // 判断是否可以发送请求
    let transformHeadersFn = ajah[symbols.fn.transformHeaders];             // 对于header变形
    let transformRequestBodyFn = ajah[symbols.fn.transformRequestBody];       // 对于request body变形
    let itShouldAbortFn = ajah[symbols.fn.itShouldAbort];       // 是否要取消请求

    let itShouldSend = itShouldSendFn.call(this, ...params);

    if (itShouldSend === false) {
      console.error(`Should Not Send: ${this[symbols.url]}`)
    } else {
      // transform headers start
      let headers = transformHeadersFn.call(this, ...params);
      for (let name in headers) {
        if (headers.hasOwnProperty(name)) {
          this.setRequestHeader(name, headers[name]);
        }
      }
      // transform request body start
      let body = transformRequestBodyFn.call(this, ...params) + '';

      originSend.apply(this, [body]);

      let itShouldAbort = itShouldAbortFn.call(this, ...params);

      itShouldAbort === true && this.abort();

    }

  } catch (err) {
    console.error(err);
  }

};

/*ajah
  .openInterceptor(function (method, url, isAsync) {
    method = method === 'DELETE' ? 'GET' : method;    // change the http method
    return [method, url, isAsync];
  })
  .itShouldOpen(function (method, url, isAsync) {
    if (method === 'DELETE') return false;            // if the method is DELETE, the it will not hand shake with the serve
    return true;
  })
  .itShouldSend(function (method, url, body, headers) {
    if (!headers.Authorization) return false;         // if this request without token field, so don't send thi request
    return true;
  })
  .transformHeaders(function (method, url, body, headers) {
    headers['Authorization'] = 'this is a mock token';    // add a headers field for this request
    return headers;
  })
  .transformRequestBody(function (method, url, body, headers) {
    if (method === 'GET') return null;                    // if method is GET, then it should not send any body.
    return body;
  })
  .transformResponseBody(function (method, url, body, headers, response) {
    // TODO
    return response;
  })
  .itShouldAbort(function (method, url, body, headers) {
    if (url.indexOf('https://') < 0) return true;         // if this request is not https, then abort this request.
    return false;
  });*/


export default ajah;