UNPKG

xhr-fetch-proxy

Version:

A proxy for XMLHttpRequest (XHR) that uses the Fetch API under the hood

158 lines (138 loc) 5.46 kB
(function (originalXMLHttpRequest) { class EventTarget { constructor() { this.eventListeners = {}; } addEventListener(event, callback) { if (!this.eventListeners[event]) { this.eventListeners[event] = []; } this.eventListeners[event].push(callback); } removeEventListener(event, callback) { if (!this.eventListeners[event]) return; const index = this.eventListeners[event].indexOf(callback); if (index !== -1) { this.eventListeners[event].splice(index, 1); } } _triggerEvent(event, ...args) { if (this.eventListeners[event]) { this.eventListeners[event].forEach(callback => callback.apply(this, args)); } } } class ProxyXMLHttpRequest extends EventTarget { constructor() { super(); this.onload = null; this.onerror = null; this.onreadystatechange = null; this.readyState = 0; this.status = 0; this.statusText = ''; this.response = null; this.res10ponseText = null; this.responseType = ''; this.responseURL = ''; this.method = null; this.url = null; this.async = true; this.requestHeaders = {}; this.controller = new AbortController(); // to handle aborts this.eventListeners = {}; this.upload = new EventTarget(); // Adding upload event listeners this._triggerEvent('readystatechange'); } open(method, url, async = true, user = null, password = null) { this.method = method; this.url = url; this.async = async; this.user = user; this.password = password; this.readyState = 1; this._triggerEvent('readystatechange'); } send(data = null) { const options = { method: this.method, headers: this.requestHeaders, body: data, signal: this.controller.signal, mode: 'cors', credentials: this.user || this.password ? 'include' : 'same-origin', }; if (this.user && this.password) { const base64Credentials = btoa(`${this.user}:${this.password}`); options.headers['Authorization'] = `Basic ${base64Credentials}`; } this.readyState = 2; this._triggerEvent('readystatechange'); fetch(this.url, options) .then(response => { this.status = response.status; this.statusText = response.statusText; this.responseURL = response.url; this._parseHeaders(response.headers); this.readyState = 3; this._triggerEvent('readystatechange'); return this._parseResponse(response); }) .then(responseData => { this.readyState = 4; this.response = responseData; this.responseText = typeof responseData === 'string' ? responseData : JSON.stringify(responseData); this._triggerEvent('readystatechange'); if (this.onload) this.onload(); }) .catch(error => { if (this.onerror) this.onerror(error); }); } setRequestHeader(header, value) { this.requestHeaders[header] = value; } abort() { this.controller.abort(); this.readyState = 0; this._triggerEvent('readystatechange'); } getResponseHeader(header) { return this.responseHeaders[header.toLowerCase()] || null; } getAllResponseHeaders() { return Object.entries(this.responseHeaders) .map(([key, value]) => `${key}: ${value}`) .join('\r\n'); } overrideMimeType(mime) { this.overrideMime = mime; } _parseHeaders(headers) { this.responseHeaders = {}; headers.forEach((value, key) => { this.responseHeaders[key.toLowerCase()] = value; }); } _parseResponse(response) { const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return response.json(); } else if (contentType && (contentType.includes('text/') || contentType.includes('xml'))) { return response.text(); } else { return response.blob(); // default to blob for binary data } } _triggerEvent(event, ...args) { super._triggerEvent(event, ...args); if (this[`on${event}`]) { this[`on${event}`].apply(this, args); } if (event.startsWith('progress') || event === 'loadstart' || event === 'loadend' || event === 'abort') { this.upload._triggerEvent(event, ...args); } } } window.XMLHttpRequest = ProxyXMLHttpRequest; })(window.XMLHttpRequest);