/* eslint-disable prefer-rest-params */ /** * 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. * * Vendored from: https://github.com/callstackincubator/rozenite/blob/402e3579878f72cbae7f8123f9ca80459dc1fe7f/packages/network-activity-plugin/src/react-native/http/xhr-interceptor.ts * Original source: https://github.com/facebook/react-native/blob/2c683c5787dd03ac15d2aad45dcc53650529ee7f/packages/react-native/src/private/devsupport/devmenu/elementinspector/XHRInterceptor.js */ 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: string, 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 let openCallback: XHRInterceptorOpenCallback | null let sendCallback: XHRInterceptorSendCallback | null let requestHeaderCallback: XHRInterceptorRequestHeaderCallback | null let headerReceivedCallback: XHRInterceptorHeaderReceivedCallback | null let responseCallback: XHRInterceptorResponseCallback | 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 }, 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) } 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) } 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: string) { if (sendCallback) { sendCallback(data, 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 ) } 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 }, }