reactotron-react-native
Version:
A development tool to explore, inspect, and diagnose your React Native apps.
199 lines (178 loc) • 6.56 kB
text/typescript
/* 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
},
}