UNPKG

4.82 kBPlain TextView Raw
1import XHRInterceptor from "react-native/Libraries/Network/XHRInterceptor"
2import type { ReactotronCore, Plugin } from "reactotron-core-client"
3
4/**
5 * Don't include the response bodies for images by default.
6 */
7const DEFAULT_CONTENT_TYPES_RX = /^(image)\/.*$/i
8
9export interface NetworkingOptions {
10 ignoreContentTypes?: RegExp
11 ignoreUrls?: RegExp
12}
13
14const DEFAULTS: NetworkingOptions = {}
15
16const networking =
17 (pluginConfig: NetworkingOptions = {}) =>
18 (reactotron: ReactotronCore) => {
19 const options = Object.assign({}, DEFAULTS, pluginConfig)
20
21 // a RegExp to suppress adding the body cuz it costs a lot to serialize
22 const ignoreContentTypes = options.ignoreContentTypes || DEFAULT_CONTENT_TYPES_RX
23
24 // a XHR call tracker
25 let reactotronCounter = 1000
26
27 // a temporary cache to hold requests so we can match up the data
28 const requestCache = {}
29
30 /**
31 * Fires when we talk to the server.
32 *
33 * @param {*} data - The data sent to the server.
34 * @param {*} instance - The XMLHTTPRequest instance.
35 */
36 function onSend(data, xhr) {
37 if (options.ignoreUrls && options.ignoreUrls.test(xhr._url)) {
38 xhr._skipReactotron = true
39 return
40 }
41
42 // bump the counter
43 reactotronCounter++
44
45 // tag
46 xhr._trackingName = reactotronCounter
47
48 // cache
49 requestCache[reactotronCounter] = {
50 data,
51 xhr,
52 stopTimer: reactotron.startTimer(),
53 }
54 }
55
56 /**
57 * Fires when the server gives us a response.
58 *
59 * @param {number} status - The HTTP response status.
60 * @param {boolean} timeout - Did we timeout?
61 * @param {*} response - The response data.
62 * @param {string} url - The URL we talked to.
63 * @param {*} type - Not sure.
64 * @param {*} xhr - The XMLHttpRequest instance.
65 */
66 function onResponse(status, timeout, response, url, type, xhr) {
67 if (xhr._skipReactotron) {
68 return
69 }
70
71 let params = null
72 const queryParamIdx = url ? url.indexOf("?") : -1
73 if (queryParamIdx > -1) {
74 params = {}
75 url
76 .substr(queryParamIdx + 1)
77 .split("&")
78 .forEach((pair) => {
79 const [key, value] = pair.split("=")
80 if (key && value !== undefined) {
81 params[key] = decodeURIComponent(value.replace(/\+/g, " "))
82 }
83 })
84 }
85
86 // fetch and clear the request data from the cache
87 const rid = xhr._trackingName
88 const cachedRequest = requestCache[rid] || { xhr }
89 requestCache[rid] = null
90
91 // assemble the request object
92 const { data, stopTimer } = cachedRequest
93 const tronRequest = {
94 url: url || cachedRequest.xhr._url,
95 method: xhr._method || null,
96 data,
97 headers: xhr._headers || null,
98 params,
99 }
100
101 // what type of content is this?
102 const contentType =
103 (xhr.responseHeaders && xhr.responseHeaders["content-type"]) ||
104 (xhr.responseHeaders && xhr.responseHeaders["Content-Type"]) ||
105 ""
106
107 const sendResponse = (responseBodyText) => {
108 let body = `~~~ skipped ~~~`
109 if (responseBodyText) {
110 try {
111 // all i am saying, is give JSON a chance...
112 body = JSON.parse(responseBodyText)
113 } catch (boom) {
114 body = response
115 }
116 }
117 const tronResponse = {
118 body,
119 status,
120 headers: xhr.responseHeaders || null,
121 }
122 ;(reactotron as any).apiResponse(tronRequest, tronResponse, stopTimer ? stopTimer() : null) // TODO: Fix
123 }
124
125 // can we use the real response?
126 const useRealResponse =
127 (typeof response === "string" || typeof response === "object") &&
128 !ignoreContentTypes.test(contentType || "")
129
130 // prepare the right body to send
131 if (useRealResponse) {
132 if (type === "blob" && typeof FileReader !== "undefined" && response) {
133 // Disable reason: FileReader should be in global scope since RN 0.54
134 // eslint-disable-next-line no-undef
135 const bReader = new FileReader()
136 const brListener = () => {
137 sendResponse(bReader.result)
138 bReader.removeEventListener("loadend", brListener)
139 }
140 bReader.addEventListener("loadend", brListener)
141 bReader.readAsText(response)
142 } else {
143 sendResponse(response)
144 }
145 } else {
146 sendResponse("")
147 }
148 }
149
150 return {
151 onConnect: () => {
152 // register our monkey-patch
153 XHRInterceptor.setSendCallback(onSend)
154 XHRInterceptor.setResponseCallback(onResponse)
155 XHRInterceptor.enableInterception()
156 },
157 } satisfies Plugin<ReactotronCore>
158 }
159export default networking