text: var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
  
  var cordovaProxy = require('cordova/exec/proxy');
  var jsUtil = require(pluginId + '.js-util');
  
  var reqMap = {};
  
  function serializeJsonData(data) {
    try {
      return JSON.stringify(data);
    } catch (err) {
      return null;
    }
  }
  
  function serializePrimitive(key, value) {
    if (value === null || value === undefined) {
      return encodeURIComponent(key) + '=';
    }
  
    return encodeURIComponent(key) + '=' + encodeURIComponent(value);
  }
  
  function serializeArray(key, values) {
    return values.map(function (value) {
      return encodeURIComponent(key) + '[]=' + encodeURIComponent(value);
    }).join('&');
  }
  
  function serializeParams(params) {
    if (params === null) return '';
  
    return Object.keys(params).map(function (key) {
      if (jsUtil.getTypeOf(params[key]) === 'Array') {
        return serializeArray(key, params[key]);
      }
  
      return serializePrimitive(key, params[key]);
    }).join('&');
  }
  
  function decodeB64(dataString) {
    var binaryString = atob(dataString);
    var bytes = new Uint8Array(binaryString.length);
  
    for (var i = 0; i < binaryString.length; ++i) {
      bytes[i] = binaryString.charCodeAt(i);
    }
  
    return bytes.buffer;
  }
  
  function processMultipartData(data) {
    if (!data) return null;
  
    var fd = new FormData();
  
    for (var i = 0; i < data.buffers.length; ++i) {
      var buffer = data.buffers[i];
      var name = data.names[i];
      var fileName = data.fileNames[i];
      var type = data.types[i];
  
      if (fileName) {
        fd.append(name, new Blob([decodeB64(buffer)], { type: type }), fileName);
      } else {
        // we assume it's plain text if no filename was given
        fd.append(name, atob(buffer));
      }
    }
  
    return fd;
  }
  
  function deserializeResponseHeaders(headers) {
    var headerMap = {};
    var arr = headers.trim().split(/[\r\n]+/);
  
    arr.forEach(function (line) {
      var parts = line.split(': ');
      var header = parts.shift().toLowerCase();
      var value = parts.join(': ');
  
      headerMap[header] = value;
    });
  
    return headerMap;
  }
  
  function getResponseData(xhr) {
    if (xhr.responseType !== 'text' || jsUtil.getTypeOf(xhr.responseText) !== 'String') {
      return xhr.response;
    }
  
    return xhr.responseText;
  }
  
  function createXhrSuccessObject(xhr) {
    return {
      url: xhr.responseURL,
      status: xhr.status,
      data: getResponseData(xhr),
      headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
    };
  }
  
  function createXhrFailureObject(xhr) {
    var obj = {};
  
    obj.headers = xhr.getAllResponseHeaders();
    obj.error = getResponseData(xhr);
    obj.error = obj.error || 'advanced-http: please check browser console for error messages';
  
    if (xhr.responseURL) obj.url = xhr.responseURL;
    if (xhr.status) obj.status = xhr.status;
  
    return obj;
  }
  
  function injectRequestIdHandler(reqId, cb) {
    return function (response) {
      delete reqMap[reqId];
      cb(response);
    }
  }
  
  function getHeaderValue(headers, headerName) {
    let result = null;
  
    Object.keys(headers).forEach(function (key) {
      if (key.toLowerCase() === headerName.toLowerCase()) {
        result = headers[key];
      }
    });
  
    return result;
  }
  
  function setDefaultContentType(headers, contentType) {
    if (getHeaderValue(headers, 'Content-Type') === null) {
      headers['Content-Type'] = contentType;
    }
  }
  
  function setHeaders(xhr, headers) {
    Object.keys(headers).forEach(function (key) {
      if (key.toLowerCase() === 'cookie') return;
  
      xhr.setRequestHeader(key, headers[key]);
    });
  }
  
  function sendRequest(method, withData, opts, success, failure) {
    var data, serializer, headers, readTimeout, followRedirect, responseType, reqId;
    var url = opts[0];
  
    if (withData) {
      data = opts[1];
      serializer = opts[2];
      headers = opts[3];
      // connect timeout not applied
      // connectTimeout = opts[4];
      readTimeout = opts[5];
      followRedirect = opts[6];
      responseType = opts[7];
      reqId = opts[8];
    } else {
      headers = opts[1];
      // connect timeout not applied
      // connectTimeout = opts[2];
      readTimeout = opts[3];
      followRedirect = opts[4];
      responseType = opts[5];
      reqId = opts[6];
    }
  
    var onSuccess = injectRequestIdHandler(reqId, success);
    var onFail = injectRequestIdHandler(reqId, failure);
  
    var processedData = null;
    var xhr = new XMLHttpRequest();
  
    reqMap[reqId] = xhr;
  
    xhr.open(method, url);
  
    if (headers.Cookie && headers.Cookie.length > 0) {
      return onFail('advanced-http: custom cookies not supported on browser platform');
    }
  
    if (!followRedirect) {
      return onFail('advanced-http: disabling follow redirect not supported on browser platform');
    }
  
    switch (serializer) {
      case 'json':
        setDefaultContentType(headers, 'application/json; charset=utf8');
        processedData = serializeJsonData(data);
  
        if (processedData === null) {
          return onFail('advanced-http: failed serializing data');
        }
  
        break;
  
      case 'utf8':
        setDefaultContentType(headers, 'text/plain; charset=utf8');
        processedData = data.text;
        break;
  
      case 'urlencoded':
        setDefaultContentType(headers, 'application/x-www-form-urlencoded');
        processedData = serializeParams(data);
        break;
  
      case 'multipart':
        const contentType = getHeaderValue(headers, 'Content-Type');
  
        // intentionally don't set a default content type
        // it's set by the browser together with the content disposition string
        if (contentType) {
          headers['Content-Type'] = contentType;
        }
  
        processedData = processMultipartData(data);
        break;
  
      case 'raw':
        setDefaultContentType(headers, 'application/octet-stream');
        processedData = data;
        break;
    }
  
    // requesting text instead of JSON because it's parsed in the response handler
    xhr.responseType = responseType === 'json' ? 'text' : responseType;
  
    // we can't set connect timeout and read timeout separately on browser platform
    xhr.timeout = readTimeout * 1000;
  
    try {
      setHeaders(xhr, headers);
    } catch(error) {
      return onFail({
        status: -1,
        error: error,
        url: url,
        headers: headers
      });
    }
  
    xhr.onerror = function () {
      return onFail(createXhrFailureObject(xhr));
    };
  
    xhr.onabort = function () {
      return onFail({
        status: -8,
        error: 'Request was aborted',
        url: url,
        headers: {}
      });
    };
  
    xhr.ontimeout = function () {
      return onFail({
        status: -4,
        error: 'Request timed out',
        url: url,
        headers: {}
      });
    };
  
    xhr.onload = function () {
      if (xhr.readyState !== xhr.DONE) return;
  
      if (xhr.status < 200 || xhr.status > 299) {
        return onFail(createXhrFailureObject(xhr));
      }
  
      return onSuccess(createXhrSuccessObject(xhr));
    };
  
    xhr.send(processedData);
  }
  
  function abort(opts, success, failure) {
    var reqId = opts[0];
    var result = false;
  
    var xhr = reqMap[reqId];
    if(xhr && xhr.readyState !== xhr.DONE){
      xhr.abort();
      result = true;
    }
  
    success({aborted: result});
  }
  
  var browserInterface = {
    get: function (success, failure, opts) {
      return sendRequest('get', false, opts, success, failure);
    },
    head: function (success, failure, opts) {
      return sendRequest('head', false, opts, success, failure);
    },
    delete: function (success, failure, opts) {
      return sendRequest('delete', false, opts, success, failure);
    },
    post: function (success, failure, opts) {
      return sendRequest('post', true, opts, success, failure);
    },
    put: function (success, failure, opts) {
      return sendRequest('put', true, opts, success, failure);
    },
    patch: function (success, failure, opts) {
      return sendRequest('patch', true, opts, success, failure);
    },
    abort: function (success, failure, opts) {
      return abort(opts, success, failure);
    },
    uploadFile: function (success, failure, opts) {
      return failure('advanced-http: function "uploadFile" not supported on browser platform');
    },
    uploadFiles: function (success, failure, opts) {
      return failure('advanced-http: function "uploadFiles" not supported on browser platform');
    },
    downloadFile: function (success, failure, opts) {
      return failure('advanced-http: function "downloadFile" not supported on browser platform');
    },
    setServerTrustMode: function (success, failure, opts) {
      return failure('advanced-http: function "setServerTrustMode" not supported on browser platform');
    },
    setClientAuthMode: function (success, failure, opts) {
      return failure('advanced-http: function "setClientAuthMode" not supported on browser platform');
    }
  };
  
  module.exports = browserInterface;
  cordovaProxy.add('CordovaHttpPlugin', browserInterface);
  