/* eslint-disable prefer-rest-params */ import { XHRPostData } from '../../shared/client'; /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * Source: https://github.com/facebook/react-native/blob/2c683c5787dd03ac15d2aad45dcc53650529ee7f/packages/react-native/src/private/devsupport/devmenu/elementinspector/XHRInterceptor.js */ const XMLHttpRequest = global.XMLHttpRequest || window.XMLHttpRequest; const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader; type XHRInterceptorOpenCallback = ( method: string, url: string, request: XMLHttpRequest ) => void; type XHRInterceptorSendCallback = ( data: XHRPostData, request: XMLHttpRequest ) => void; type XHRInterceptorRequestHeaderCallback = ( header: string, value: string, request: XMLHttpRequest ) => void; type XHRInterceptorHeaderReceivedCallback = ( responseContentType: string | undefined, responseSize: number | undefined, allHeaders: string, request: XMLHttpRequest ) => void; type XHRInterceptorResponseCallback = ( status: number, timeout: number, response: string, responseURL: string, responseType: string, request: XMLHttpRequest ) => void; type XHRInterceptorOverrideCallback = (request: XMLHttpRequest) => void; let openCallback: XHRInterceptorOpenCallback | null; let sendCallback: XHRInterceptorSendCallback | null; let requestHeaderCallback: XHRInterceptorRequestHeaderCallback | null; let headerReceivedCallback: XHRInterceptorHeaderReceivedCallback | null; let responseCallback: XHRInterceptorResponseCallback | null; let overrideCallback: XHRInterceptorOverrideCallback | null; let isInterceptorEnabled = false; /** * A network interceptor which monkey-patches XMLHttpRequest methods * to gather all network requests/responses, in order to show their * information in the React Native inspector development tool. * This supports interception with XMLHttpRequest API, including Fetch API * and any other third party libraries that depend on XMLHttpRequest. */ export const XHRInterceptor = { /** * Invoked before XMLHttpRequest.open(...) is called. */ setOpenCallback(callback: XHRInterceptorOpenCallback) { openCallback = callback; }, /** * Invoked before XMLHttpRequest.send(...) is called. */ setSendCallback(callback: XHRInterceptorSendCallback) { sendCallback = callback; }, /** * Invoked after xhr's readyState becomes xhr.HEADERS_RECEIVED. */ setHeaderReceivedCallback(callback: XHRInterceptorHeaderReceivedCallback) { headerReceivedCallback = callback; }, /** * Invoked after xhr's readyState becomes xhr.DONE. */ setResponseCallback(callback: XHRInterceptorResponseCallback) { responseCallback = callback; }, /** * Invoked before XMLHttpRequest.setRequestHeader(...) is called. */ setRequestHeaderCallback(callback: XHRInterceptorRequestHeaderCallback) { requestHeaderCallback = callback; }, /** * Invoked before XMLHttpRequest.send(...) is called. */ setOverrideCallback(callback: XHRInterceptorOverrideCallback) { overrideCallback = callback; }, isInterceptorEnabled(): boolean { return isInterceptorEnabled; }, enableInterception() { if (isInterceptorEnabled) { return; } // Override `open` method for all XHR requests to intercept the request // method and url, then pass them through the `openCallback`. // $FlowFixMe[cannot-write] // $FlowFixMe[missing-this-annot] XMLHttpRequest.prototype.open = function (method: string, url: string) { if (openCallback) { openCallback(method, url, this); } // @ts-expect-error - Needed for the originalXHROpen to work originalXHROpen.apply(this, arguments); }; // Override `setRequestHeader` method for all XHR requests to intercept // the request headers, then pass them through the `requestHeaderCallback`. // $FlowFixMe[cannot-write] // $FlowFixMe[missing-this-annot] XMLHttpRequest.prototype.setRequestHeader = function ( header: string, value: string ) { if (requestHeaderCallback) { requestHeaderCallback(header, value, this); } // @ts-expect-error - Needed for the originalXHRSetRequestHeader to work originalXHRSetRequestHeader.apply(this, arguments); }; // Override `send` method of all XHR requests to intercept the data sent, // register listeners to intercept the response, and invoke the callbacks. // $FlowFixMe[cannot-write] // $FlowFixMe[missing-this-annot] XMLHttpRequest.prototype.send = function (data: XHRPostData) { if (sendCallback) { sendCallback(data, this); } if (overrideCallback) { overrideCallback(this); } if (this.addEventListener) { this.addEventListener( 'readystatechange', () => { if (!isInterceptorEnabled) { return; } if (this.readyState === this.HEADERS_RECEIVED) { const contentTypeString = this.getResponseHeader('Content-Type'); const contentLengthString = this.getResponseHeader('Content-Length'); let responseContentType, responseSize; if (contentTypeString) { responseContentType = contentTypeString.split(';')[0]; } if (contentLengthString) { responseSize = parseInt(contentLengthString, 10); } if (headerReceivedCallback) { headerReceivedCallback( responseContentType, responseSize, this.getAllResponseHeaders(), this ); } } if (this.readyState === this.DONE) { if (responseCallback) { responseCallback( this.status, this.timeout, this.response, this.responseURL, this.responseType, this ); } } }, false ); } // @ts-expect-error - Needed for the originalXHRSend to work originalXHRSend.apply(this, arguments); }; isInterceptorEnabled = true; }, // Unpatch XMLHttpRequest methods and remove the callbacks. disableInterception() { if (!isInterceptorEnabled) { return; } isInterceptorEnabled = false; // $FlowFixMe[cannot-write] XMLHttpRequest.prototype.send = originalXHRSend; // $FlowFixMe[cannot-write] XMLHttpRequest.prototype.open = originalXHROpen; // $FlowFixMe[cannot-write] XMLHttpRequest.prototype.setRequestHeader = originalXHRSetRequestHeader; responseCallback = null; openCallback = null; sendCallback = null; headerReceivedCallback = null; requestHeaderCallback = null; overrideCallback = null; }, };