execbro 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/README.md +5 -2
  2. package/build/__tests__/helpers/mock-harness.d.ts +35 -0
  3. package/build/__tests__/helpers/mock-harness.d.ts.map +1 -0
  4. package/build/__tests__/helpers/mock-harness.js +83 -0
  5. package/build/__tests__/helpers/mock-harness.js.map +1 -0
  6. package/build/__tests__/unit/androidEmulatorAvds.test.js +2 -1
  7. package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -1
  8. package/build/__tests__/unit/buildToken.test.js +20 -3
  9. package/build/__tests__/unit/buildToken.test.js.map +1 -1
  10. package/build/__tests__/unit/commandInjection.test.d.ts +2 -0
  11. package/build/__tests__/unit/commandInjection.test.d.ts.map +1 -0
  12. package/build/__tests__/unit/commandInjection.test.js +55 -0
  13. package/build/__tests__/unit/commandInjection.test.js.map +1 -0
  14. package/build/__tests__/unit/deviceResolver.test.js +2 -0
  15. package/build/__tests__/unit/deviceResolver.test.js.map +1 -1
  16. package/build/__tests__/unit/deviceResolverStaleRetry.test.js +1 -0
  17. package/build/__tests__/unit/deviceResolverStaleRetry.test.js.map +1 -1
  18. package/build/__tests__/unit/errorCategorization.test.js +14 -0
  19. package/build/__tests__/unit/errorCategorization.test.js.map +1 -1
  20. package/build/__tests__/unit/fingerprint.test.js +2 -2
  21. package/build/__tests__/unit/fingerprint.test.js.map +1 -1
  22. package/build/__tests__/unit/inputTarget.test.js +46 -0
  23. package/build/__tests__/unit/inputTarget.test.js.map +1 -1
  24. package/build/__tests__/unit/logSourceAndroid.test.js +5 -5
  25. package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -1
  26. package/build/__tests__/unit/logSourceIos.test.js +10 -6
  27. package/build/__tests__/unit/logSourceIos.test.js.map +1 -1
  28. package/build/__tests__/unit/logStack.test.d.ts +2 -0
  29. package/build/__tests__/unit/logStack.test.d.ts.map +1 -0
  30. package/build/__tests__/unit/logStack.test.js +86 -0
  31. package/build/__tests__/unit/logStack.test.js.map +1 -0
  32. package/build/__tests__/unit/logSymbolication.test.d.ts +2 -0
  33. package/build/__tests__/unit/logSymbolication.test.d.ts.map +1 -0
  34. package/build/__tests__/unit/logSymbolication.test.js +215 -0
  35. package/build/__tests__/unit/logSymbolication.test.js.map +1 -0
  36. package/build/__tests__/unit/measureComponentResolution.test.d.ts +2 -0
  37. package/build/__tests__/unit/measureComponentResolution.test.d.ts.map +1 -0
  38. package/build/__tests__/unit/measureComponentResolution.test.js +111 -0
  39. package/build/__tests__/unit/measureComponentResolution.test.js.map +1 -0
  40. package/build/__tests__/unit/metroIdentity.test.d.ts +2 -0
  41. package/build/__tests__/unit/metroIdentity.test.d.ts.map +1 -0
  42. package/build/__tests__/unit/metroIdentity.test.js +41 -0
  43. package/build/__tests__/unit/metroIdentity.test.js.map +1 -0
  44. package/build/__tests__/unit/mockRulePush.test.d.ts +2 -0
  45. package/build/__tests__/unit/mockRulePush.test.d.ts.map +1 -0
  46. package/build/__tests__/unit/mockRulePush.test.js +65 -0
  47. package/build/__tests__/unit/mockRulePush.test.js.map +1 -0
  48. package/build/__tests__/unit/mockRules.test.d.ts +2 -0
  49. package/build/__tests__/unit/mockRules.test.d.ts.map +1 -0
  50. package/build/__tests__/unit/mockRules.test.js +111 -0
  51. package/build/__tests__/unit/mockRules.test.js.map +1 -0
  52. package/build/__tests__/unit/nativeLogs.test.js +8 -2
  53. package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
  54. package/build/__tests__/unit/netInfoPatch.test.d.ts +2 -0
  55. package/build/__tests__/unit/netInfoPatch.test.d.ts.map +1 -0
  56. package/build/__tests__/unit/netInfoPatch.test.js +46 -0
  57. package/build/__tests__/unit/netInfoPatch.test.js.map +1 -0
  58. package/build/__tests__/unit/networkInterceptorXhr.test.d.ts +2 -0
  59. package/build/__tests__/unit/networkInterceptorXhr.test.d.ts.map +1 -0
  60. package/build/__tests__/unit/networkInterceptorXhr.test.js +440 -0
  61. package/build/__tests__/unit/networkInterceptorXhr.test.js.map +1 -0
  62. package/build/__tests__/unit/networkMock.test.d.ts +2 -0
  63. package/build/__tests__/unit/networkMock.test.d.ts.map +1 -0
  64. package/build/__tests__/unit/networkMock.test.js +183 -0
  65. package/build/__tests__/unit/networkMock.test.js.map +1 -0
  66. package/build/__tests__/unit/networkMockRender.test.d.ts +2 -0
  67. package/build/__tests__/unit/networkMockRender.test.d.ts.map +1 -0
  68. package/build/__tests__/unit/networkMockRender.test.js +131 -0
  69. package/build/__tests__/unit/networkMockRender.test.js.map +1 -0
  70. package/build/__tests__/unit/networkMockTamper.test.d.ts +2 -0
  71. package/build/__tests__/unit/networkMockTamper.test.d.ts.map +1 -0
  72. package/build/__tests__/unit/networkMockTamper.test.js +162 -0
  73. package/build/__tests__/unit/networkMockTamper.test.js.map +1 -0
  74. package/build/__tests__/unit/networkMockTool.test.d.ts +2 -0
  75. package/build/__tests__/unit/networkMockTool.test.d.ts.map +1 -0
  76. package/build/__tests__/unit/networkMockTool.test.js +69 -0
  77. package/build/__tests__/unit/networkMockTool.test.js.map +1 -0
  78. package/build/__tests__/unit/networkReplay.test.d.ts +2 -0
  79. package/build/__tests__/unit/networkReplay.test.d.ts.map +1 -0
  80. package/build/__tests__/unit/networkReplay.test.js +86 -0
  81. package/build/__tests__/unit/networkReplay.test.js.map +1 -0
  82. package/build/__tests__/unit/screenState.test.js +63 -2
  83. package/build/__tests__/unit/screenState.test.js.map +1 -1
  84. package/build/__tests__/unit/serialQueue.test.d.ts +2 -0
  85. package/build/__tests__/unit/serialQueue.test.d.ts.map +1 -0
  86. package/build/__tests__/unit/serialQueue.test.js +77 -0
  87. package/build/__tests__/unit/serialQueue.test.js.map +1 -0
  88. package/build/__tests__/unit/swipeDiagnosis.test.d.ts +2 -0
  89. package/build/__tests__/unit/swipeDiagnosis.test.d.ts.map +1 -0
  90. package/build/__tests__/unit/swipeDiagnosis.test.js +47 -0
  91. package/build/__tests__/unit/swipeDiagnosis.test.js.map +1 -0
  92. package/build/__tests__/unit/swipeDirection.test.js +30 -0
  93. package/build/__tests__/unit/swipeDirection.test.js.map +1 -1
  94. package/build/__tests__/unit/symbolicate.test.js +36 -0
  95. package/build/__tests__/unit/symbolicate.test.js.map +1 -1
  96. package/build/__tests__/unit/tap.test.js +0 -48
  97. package/build/__tests__/unit/tap.test.js.map +1 -1
  98. package/build/__tests__/unit/textEntry.test.js +53 -0
  99. package/build/__tests__/unit/textEntry.test.js.map +1 -1
  100. package/build/__tests__/unit/toolArgInjection.test.d.ts +2 -0
  101. package/build/__tests__/unit/toolArgInjection.test.d.ts.map +1 -0
  102. package/build/__tests__/unit/toolArgInjection.test.js +86 -0
  103. package/build/__tests__/unit/toolArgInjection.test.js.map +1 -0
  104. package/build/core/android.d.ts.map +1 -1
  105. package/build/core/android.js +54 -111
  106. package/build/core/android.js.map +1 -1
  107. package/build/core/androidForeground.d.ts +4 -0
  108. package/build/core/androidForeground.d.ts.map +1 -0
  109. package/build/core/androidForeground.js +34 -0
  110. package/build/core/androidForeground.js.map +1 -0
  111. package/build/core/androidSystemBars.d.ts +1 -1
  112. package/build/core/androidSystemBars.d.ts.map +1 -1
  113. package/build/core/androidSystemBars.js +5 -5
  114. package/build/core/androidSystemBars.js.map +1 -1
  115. package/build/core/appRequest.d.ts +1 -0
  116. package/build/core/appRequest.d.ts.map +1 -1
  117. package/build/core/appRequest.js +5 -2
  118. package/build/core/appRequest.js.map +1 -1
  119. package/build/core/buildInfo.d.ts +3 -0
  120. package/build/core/buildInfo.d.ts.map +1 -0
  121. package/build/core/buildInfo.js +6 -0
  122. package/build/core/buildInfo.js.map +1 -0
  123. package/build/core/componentSource.d.ts.map +1 -1
  124. package/build/core/componentSource.js +1 -1
  125. package/build/core/componentSource.js.map +1 -1
  126. package/build/core/connection.d.ts +6 -0
  127. package/build/core/connection.d.ts.map +1 -1
  128. package/build/core/connection.js +60 -5
  129. package/build/core/connection.js.map +1 -1
  130. package/build/core/deviceResolver.d.ts.map +1 -1
  131. package/build/core/deviceResolver.js +7 -1
  132. package/build/core/deviceResolver.js.map +1 -1
  133. package/build/core/exec.d.ts +5 -0
  134. package/build/core/exec.d.ts.map +1 -1
  135. package/build/core/exec.js +34 -1
  136. package/build/core/exec.js.map +1 -1
  137. package/build/core/fingerprint.d.ts.map +1 -1
  138. package/build/core/fingerprint.js +3 -3
  139. package/build/core/fingerprint.js.map +1 -1
  140. package/build/core/guides.d.ts.map +1 -1
  141. package/build/core/guides.js +21 -1
  142. package/build/core/guides.js.map +1 -1
  143. package/build/core/inputTarget.d.ts.map +1 -1
  144. package/build/core/inputTarget.js +42 -2
  145. package/build/core/inputTarget.js.map +1 -1
  146. package/build/core/ios.d.ts.map +1 -1
  147. package/build/core/ios.js +14 -17
  148. package/build/core/ios.js.map +1 -1
  149. package/build/core/jsLogEvents.d.ts.map +1 -1
  150. package/build/core/jsLogEvents.js +1 -0
  151. package/build/core/jsLogEvents.js.map +1 -1
  152. package/build/core/keyboardRaise.d.ts.map +1 -1
  153. package/build/core/keyboardRaise.js +3 -3
  154. package/build/core/keyboardRaise.js.map +1 -1
  155. package/build/core/logEventFormat.d.ts +2 -0
  156. package/build/core/logEventFormat.d.ts.map +1 -1
  157. package/build/core/logEventFormat.js +6 -1
  158. package/build/core/logEventFormat.js.map +1 -1
  159. package/build/core/logEvents.d.ts +2 -1
  160. package/build/core/logEvents.d.ts.map +1 -1
  161. package/build/core/logEvents.js.map +1 -1
  162. package/build/core/logSourceAndroid.d.ts +1 -1
  163. package/build/core/logSourceAndroid.d.ts.map +1 -1
  164. package/build/core/logSourceAndroid.js +9 -9
  165. package/build/core/logSourceAndroid.js.map +1 -1
  166. package/build/core/logSourceIos.d.ts +1 -1
  167. package/build/core/logSourceIos.d.ts.map +1 -1
  168. package/build/core/logSourceIos.js +9 -9
  169. package/build/core/logSourceIos.js.map +1 -1
  170. package/build/core/logStack.d.ts +6 -0
  171. package/build/core/logStack.d.ts.map +1 -0
  172. package/build/core/logStack.js +44 -0
  173. package/build/core/logStack.js.map +1 -0
  174. package/build/core/logSymbolication.d.ts +11 -0
  175. package/build/core/logSymbolication.d.ts.map +1 -0
  176. package/build/core/logSymbolication.js +91 -0
  177. package/build/core/logSymbolication.js.map +1 -0
  178. package/build/core/measureComponent.d.ts.map +1 -1
  179. package/build/core/measureComponent.js +45 -11
  180. package/build/core/measureComponent.js.map +1 -1
  181. package/build/core/metroIdentity.d.ts +8 -0
  182. package/build/core/metroIdentity.d.ts.map +1 -0
  183. package/build/core/metroIdentity.js +65 -0
  184. package/build/core/metroIdentity.js.map +1 -0
  185. package/build/core/mockRules.d.ts +30 -0
  186. package/build/core/mockRules.d.ts.map +1 -0
  187. package/build/core/mockRules.js +200 -0
  188. package/build/core/mockRules.js.map +1 -0
  189. package/build/core/nativeInputValue.js +6 -6
  190. package/build/core/nativeInputValue.js.map +1 -1
  191. package/build/core/nativeLogs.js +2 -2
  192. package/build/core/nativeLogs.js.map +1 -1
  193. package/build/core/netInfoPatch.d.ts +5 -0
  194. package/build/core/netInfoPatch.d.ts.map +1 -0
  195. package/build/core/netInfoPatch.js +88 -0
  196. package/build/core/netInfoPatch.js.map +1 -0
  197. package/build/core/network.d.ts.map +1 -1
  198. package/build/core/network.js +9 -0
  199. package/build/core/network.js.map +1 -1
  200. package/build/core/networkInterceptor.d.ts +6 -0
  201. package/build/core/networkInterceptor.d.ts.map +1 -1
  202. package/build/core/networkInterceptor.js +660 -13
  203. package/build/core/networkInterceptor.js.map +1 -1
  204. package/build/core/replay.d.ts +14 -0
  205. package/build/core/replay.d.ts.map +1 -0
  206. package/build/core/replay.js +14 -0
  207. package/build/core/replay.js.map +1 -0
  208. package/build/core/screenSpaceDevice.d.ts +5 -0
  209. package/build/core/screenSpaceDevice.d.ts.map +1 -1
  210. package/build/core/screenSpaceDevice.js +18 -0
  211. package/build/core/screenSpaceDevice.js.map +1 -1
  212. package/build/core/screenState.d.ts +8 -0
  213. package/build/core/screenState.d.ts.map +1 -1
  214. package/build/core/screenState.js +245 -32
  215. package/build/core/screenState.js.map +1 -1
  216. package/build/core/serialQueue.d.ts +6 -0
  217. package/build/core/serialQueue.d.ts.map +1 -0
  218. package/build/core/serialQueue.js +18 -0
  219. package/build/core/serialQueue.js.map +1 -0
  220. package/build/core/swipeDiagnosis.d.ts +17 -0
  221. package/build/core/swipeDiagnosis.d.ts.map +1 -0
  222. package/build/core/swipeDiagnosis.js +179 -0
  223. package/build/core/swipeDiagnosis.js.map +1 -0
  224. package/build/core/symbolicate.d.ts +2 -2
  225. package/build/core/symbolicate.d.ts.map +1 -1
  226. package/build/core/symbolicate.js +2 -2
  227. package/build/core/symbolicate.js.map +1 -1
  228. package/build/core/telemetry.d.ts.map +1 -1
  229. package/build/core/telemetry.js +5 -3
  230. package/build/core/telemetry.js.map +1 -1
  231. package/build/core/textEntry.d.ts +2 -0
  232. package/build/core/textEntry.d.ts.map +1 -1
  233. package/build/core/textEntry.js +30 -3
  234. package/build/core/textEntry.js.map +1 -1
  235. package/build/core/types.d.ts +11 -0
  236. package/build/core/types.d.ts.map +1 -1
  237. package/build/index.d.ts.map +1 -1
  238. package/build/index.js +70 -9
  239. package/build/index.js.map +1 -1
  240. package/build/pro/tap.d.ts +6 -1
  241. package/build/pro/tap.d.ts.map +1 -1
  242. package/build/pro/tap.js +12 -3
  243. package/build/pro/tap.js.map +1 -1
  244. package/build/tools/componentTools.d.ts.map +1 -1
  245. package/build/tools/componentTools.js +12 -4
  246. package/build/tools/componentTools.js.map +1 -1
  247. package/build/tools/connectionTools.d.ts.map +1 -1
  248. package/build/tools/connectionTools.js +18 -0
  249. package/build/tools/connectionTools.js.map +1 -1
  250. package/build/tools/deviceTools.d.ts.map +1 -1
  251. package/build/tools/deviceTools.js +4 -1
  252. package/build/tools/deviceTools.js.map +1 -1
  253. package/build/tools/interactionTools.d.ts.map +1 -1
  254. package/build/tools/interactionTools.js +56 -14
  255. package/build/tools/interactionTools.js.map +1 -1
  256. package/build/tools/logTools.d.ts.map +1 -1
  257. package/build/tools/logTools.js +16 -3
  258. package/build/tools/logTools.js.map +1 -1
  259. package/build/tools/networkTools.d.ts.map +1 -1
  260. package/build/tools/networkTools.js +196 -4
  261. package/build/tools/networkTools.js.map +1 -1
  262. package/build/tools/requestTools.d.ts.map +1 -1
  263. package/build/tools/requestTools.js +93 -26
  264. package/build/tools/requestTools.js.map +1 -1
  265. package/package.json +3 -3
  266. package/tools.json +37 -1
  267. package/build/__tests__/helpers/fake-cdp-server.d.ts +0 -53
  268. package/build/__tests__/helpers/fake-cdp-server.d.ts.map +0 -1
  269. package/build/__tests__/helpers/fake-cdp-server.js +0 -116
  270. package/build/__tests__/helpers/fake-cdp-server.js.map +0 -1
  271. package/build/__tests__/helpers/tap-test-helpers.d.ts +0 -13
  272. package/build/__tests__/helpers/tap-test-helpers.d.ts.map +0 -1
  273. package/build/__tests__/helpers/tap-test-helpers.js +0 -103
  274. package/build/__tests__/helpers/tap-test-helpers.js.map +0 -1
  275. package/build/__tests__/integration/auto-reconnect.test.d.ts +0 -2
  276. package/build/__tests__/integration/auto-reconnect.test.d.ts.map +0 -1
  277. package/build/__tests__/integration/auto-reconnect.test.js +0 -108
  278. package/build/__tests__/integration/auto-reconnect.test.js.map +0 -1
  279. package/build/__tests__/integration/connection-health.test.d.ts +0 -2
  280. package/build/__tests__/integration/connection-health.test.d.ts.map +0 -1
  281. package/build/__tests__/integration/connection-health.test.js +0 -155
  282. package/build/__tests__/integration/connection-health.test.js.map +0 -1
  283. package/build/__tests__/integration/execute-in-app.test.d.ts +0 -2
  284. package/build/__tests__/integration/execute-in-app.test.d.ts.map +0 -1
  285. package/build/__tests__/integration/execute-in-app.test.js +0 -114
  286. package/build/__tests__/integration/execute-in-app.test.js.map +0 -1
  287. package/build/__tests__/integration/native-logs.test.d.ts +0 -2
  288. package/build/__tests__/integration/native-logs.test.d.ts.map +0 -1
  289. package/build/__tests__/integration/native-logs.test.js +0 -34
  290. package/build/__tests__/integration/native-logs.test.js.map +0 -1
  291. package/build/__tests__/integration/per-call-timeout.test.d.ts +0 -2
  292. package/build/__tests__/integration/per-call-timeout.test.d.ts.map +0 -1
  293. package/build/__tests__/integration/per-call-timeout.test.js +0 -99
  294. package/build/__tests__/integration/per-call-timeout.test.js.map +0 -1
  295. package/build/__tests__/integration/socket-close-midflight.test.d.ts +0 -2
  296. package/build/__tests__/integration/socket-close-midflight.test.d.ts.map +0 -1
  297. package/build/__tests__/integration/socket-close-midflight.test.js +0 -96
  298. package/build/__tests__/integration/socket-close-midflight.test.js.map +0 -1
  299. package/build/__tests__/integration/stale-target-probe.test.d.ts +0 -2
  300. package/build/__tests__/integration/stale-target-probe.test.d.ts.map +0 -1
  301. package/build/__tests__/integration/stale-target-probe.test.js +0 -70
  302. package/build/__tests__/integration/stale-target-probe.test.js.map +0 -1
  303. package/build/__tests__/integration/tap-strategies.test.d.ts +0 -2
  304. package/build/__tests__/integration/tap-strategies.test.d.ts.map +0 -1
  305. package/build/__tests__/integration/tap-strategies.test.js +0 -296
  306. package/build/__tests__/integration/tap-strategies.test.js.map +0 -1
  307. package/build/__tests__/integration/text-input.test.d.ts +0 -2
  308. package/build/__tests__/integration/text-input.test.d.ts.map +0 -1
  309. package/build/__tests__/integration/text-input.test.js +0 -142
  310. package/build/__tests__/integration/text-input.test.js.map +0 -1
  311. package/build/__tests__/integration/tools.test.d.ts +0 -2
  312. package/build/__tests__/integration/tools.test.d.ts.map +0 -1
  313. package/build/__tests__/integration/tools.test.js +0 -221
  314. package/build/__tests__/integration/tools.test.js.map +0 -1
@@ -1,4 +1,7 @@
1
1
  import { getNextMessageId, getEpoch } from "./state.js";
2
+ import { recordHit } from "./mockRules.js";
3
+ export const REQUEST_BODY_CAP = 8 * 1024;
4
+ export const RESPONSE_BODY_CAP = 32 * 1024;
2
5
  export function getInterceptorScript() {
3
6
  return `(function() { try {
4
7
  if (globalThis.__RN_NET_INJECTED__) return;
@@ -6,6 +9,8 @@ export function getInterceptorScript() {
6
9
 
7
10
  var _counter = 0;
8
11
  var _prefix = 'js-' + Math.random().toString(36).substring(2, 6) + '-';
12
+ var _REQ_CAP = ${REQUEST_BODY_CAP};
13
+ var _RES_CAP = ${RESPONSE_BODY_CAP};
9
14
 
10
15
  function _genId() {
11
16
  return _prefix + (++_counter);
@@ -16,17 +21,538 @@ export function getInterceptorScript() {
16
21
  // flips this flag via Runtime.evaluate once it detects __RN_AI_DEVTOOLS__,
17
22
  // so the wrapper stops emitting debug lines and CDP traffic.
18
23
  if (globalThis.__RN_NET_DISABLED__) return;
24
+ _reportAlways(evt);
25
+ }
26
+
27
+ // Mock events bypass the SDK suppression gate. That flag exists so two
28
+ // capture layers do not both report the same request; a mock is not
29
+ // captured traffic, and the SDK knows nothing about it. Suppressing it
30
+ // would hide altered traffic from the agent and freeze every server-side
31
+ // hit counter at zero — the one thing a mock must never do.
32
+ function _reportAlways(evt) {
19
33
  try {
20
34
  console.debug('__RN_NET__:' + JSON.stringify(evt));
21
35
  } catch(e) {}
22
36
  }
23
37
 
38
+ // Bodies travel inline in a console.debug line shared with the app's own
39
+ // output, so they are capped here rather than at read time.
40
+ function _cap(s, limit) {
41
+ if (typeof s !== 'string') return undefined;
42
+ if (s.length <= limit) return s;
43
+ return s.slice(0, limit) + '... [truncated ' + (s.length - limit) + ' bytes]';
44
+ }
45
+
46
+ function _bodyToString(b) {
47
+ if (b === null || b === undefined) return undefined;
48
+ try {
49
+ if (typeof b === 'string') return b;
50
+ if (typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams) return b.toString();
51
+ if (typeof FormData !== 'undefined' && b instanceof FormData) return '[FormData]';
52
+ if (typeof Blob !== 'undefined' && b instanceof Blob) {
53
+ return '[binary body, ' + (typeof b.size === 'number' ? b.size : 0) + ' bytes]';
54
+ }
55
+ if (typeof ArrayBuffer !== 'undefined' && b instanceof ArrayBuffer) {
56
+ return '[binary body, ' + b.byteLength + ' bytes]';
57
+ }
58
+ if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView && ArrayBuffer.isView(b)) {
59
+ return '[binary body, ' + b.byteLength + ' bytes]';
60
+ }
61
+ if (typeof b === 'object') {
62
+ // Duck-typed fallbacks: instanceof misses values built in another
63
+ // realm, and String(binary) yields a useless '[object ArrayBuffer]'.
64
+ if (typeof b.getParts === 'function') return '[FormData]';
65
+ if (typeof b.byteLength === 'number') return '[binary body, ' + b.byteLength + ' bytes]';
66
+ if (typeof b.size === 'number') return '[binary body, ' + b.size + ' bytes]';
67
+ try { return JSON.stringify(b); } catch(e) {}
68
+ }
69
+ return String(b);
70
+ } catch(e) { return undefined; }
71
+ }
72
+
73
+ function _parseHeaders(raw) {
74
+ var out = {};
75
+ if (typeof raw !== 'string' || raw.length === 0) return out;
76
+ var lines = raw.split('\\n');
77
+ for (var i = 0; i < lines.length; i++) {
78
+ var idx = lines[i].indexOf(':');
79
+ if (idx <= 0) continue;
80
+ out[lines[i].slice(0, idx).trim().toLowerCase()] = lines[i].slice(idx + 1).trim();
81
+ }
82
+ return out;
83
+ }
84
+
85
+ function _isTextual(ct) {
86
+ if (!ct) return false;
87
+ var v = String(ct).toLowerCase();
88
+ return v.indexOf('json') >= 0 || v.indexOf('text/') === 0 || v.indexOf('xml') >= 0 ||
89
+ v.indexOf('javascript') >= 0 || v.indexOf('x-www-form-urlencoded') >= 0;
90
+ }
91
+
92
+ function _responseBody(xhr) {
93
+ try {
94
+ var rt = xhr.responseType;
95
+ if (rt === '' || rt === undefined || rt === 'text') return _cap(xhr.responseText, _RES_CAP);
96
+ if (rt === 'json') return _cap(JSON.stringify(xhr.response), _RES_CAP);
97
+ if (rt === 'arraybuffer') {
98
+ var ab = xhr.response;
99
+ return '[binary response, ' + ((ab && ab.byteLength) || 0) + ' bytes]';
100
+ }
101
+ if (rt === 'blob') {
102
+ var b = xhr.response;
103
+ return '[binary response, ' + ((b && b.size) || 0) + ' bytes]';
104
+ }
105
+ return undefined;
106
+ } catch(e) { return undefined; }
107
+ }
108
+
109
+ // RN's fetch polyfill sets responseType='blob', so a JSON response arrives
110
+ // as a Blob and responseText throws. Read it out of band and patch the
111
+ // entry with a follow-up event rather than reporting a binary placeholder.
112
+ function _readBlobText(id, blob) {
113
+ try {
114
+ var reader = new FileReader();
115
+ reader.onload = function() {
116
+ try {
117
+ if (typeof reader.result === 'string') {
118
+ _report({type: 'body', id: id, body: _cap(reader.result, _RES_CAP)});
119
+ }
120
+ } catch(e) {}
121
+ };
122
+ reader.readAsText(blob);
123
+ } catch(e) {}
124
+ }
125
+
126
+ // ---- mock layer ------------------------------------------------------
127
+ // Rules are pushed from the server (buildMockPushScript). Never reset here:
128
+ // a re-injection into a live context must not drop them.
129
+ if (!globalThis.__RN_NET_MOCKS__) globalThis.__RN_NET_MOCKS__ = [];
130
+
131
+ function _ruleMatches(rule, method, url) {
132
+ try {
133
+ if (rule.method && String(rule.method).toUpperCase() !== method) return false;
134
+ var pat = String(rule.url === undefined || rule.url === null ? '' : rule.url);
135
+ if (pat.length > 1 && pat.charAt(0) === '/' && pat.charAt(pat.length - 1) === '/') {
136
+ // Slash-wrapped means regex. Compiled per call rather than cached:
137
+ // the rule list is short and re-pushed on every context creation.
138
+ // The server rejects catastrophic patterns before they get here.
139
+ return new RegExp(pat.slice(1, -1)).test(url);
140
+ }
141
+ return url.indexOf(pat) !== -1;
142
+ } catch (e) { return false; }
143
+ }
144
+
145
+ function _matchRule(method, url) {
146
+ var rules = globalThis.__RN_NET_MOCKS__;
147
+ if (!rules || !rules.length) return null;
148
+ for (var i = 0; i < rules.length; i++) {
149
+ var r = rules[i];
150
+ if (!r || r.__spent) continue;
151
+ if (_ruleMatches(r, method, url)) {
152
+ if (typeof r.times === 'number') {
153
+ r.__used = (r.__used || 0) + 1;
154
+ if (r.__used >= r.times) r.__spent = true;
155
+ }
156
+ return r;
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+
162
+ // A plain assignment is silently dropped when the prototype exposes a
163
+ // getter with no setter — which is exactly how React Native's
164
+ // XMLHttpRequest defines responseText and response. Verify the write took
165
+ // and fall back to an own data property, which shadows the accessor.
166
+ function _setProp(obj, name, value) {
167
+ try {
168
+ obj[name] = value;
169
+ if (obj[name] === value) return;
170
+ } catch (e) {}
171
+ try {
172
+ Object.defineProperty(obj, name, {
173
+ configurable: true, enumerable: true, writable: true, value: value
174
+ });
175
+ } catch (e) {}
176
+ }
177
+
178
+ function _headersToText(headers) {
179
+ var out = '';
180
+ if (!headers || typeof headers !== 'object') return out;
181
+ try {
182
+ for (var k in headers) {
183
+ if (Object.prototype.hasOwnProperty.call(headers, k)) {
184
+ out += String(k).toLowerCase() + ': ' + String(headers[k]) + '\\r\\n';
185
+ }
186
+ }
187
+ } catch (e) {}
188
+ return out;
189
+ }
190
+
191
+ // getAllResponseHeaders / getResponseHeader are methods, not properties, so
192
+ // _setProp cannot reach them. Shadow them per instance for this response.
193
+ function _installMockHeaders(xhr, headersText) {
194
+ try {
195
+ var text = headersText || '';
196
+ Object.defineProperty(xhr, 'getAllResponseHeaders', {
197
+ configurable: true, enumerable: false, writable: true,
198
+ value: function () { return text; }
199
+ });
200
+ Object.defineProperty(xhr, 'getResponseHeader', {
201
+ configurable: true, enumerable: false, writable: true,
202
+ value: function (name) {
203
+ var v = _parseHeaders(text)[String(name).toLowerCase()];
204
+ return (v === undefined) ? null : v;
205
+ }
206
+ });
207
+ } catch (e) {}
208
+ }
209
+
210
+ // Fires the on<event> property handler and every addEventListener listener,
211
+ // which is what a real XHR does. dispatchEvent is deliberately not used:
212
+ // RN's EventTarget rejects a plain object, and the listener list below is
213
+ // complete because addEventListener is patched on the prototype.
214
+ function _fire(xhr, type) {
215
+ try {
216
+ var evt = { type: type, target: xhr, currentTarget: xhr };
217
+ var prop = xhr['on' + type];
218
+ if (typeof prop === 'function') { try { prop.call(xhr, evt); } catch (e) {} }
219
+ var ls = xhr.__rn_net_listeners__ && xhr.__rn_net_listeners__[type];
220
+ if (ls) {
221
+ var copy = ls.slice();
222
+ for (var i = 0; i < copy.length; i++) {
223
+ try { copy[i].call(xhr, evt); } catch (e) {}
224
+ }
225
+ }
226
+ } catch (e) {}
227
+ }
228
+
229
+ /**
230
+ * Writes a synthetic response onto the app's own XHR and fires its
231
+ * handlers, so the app reads mocked values through the normal API.
232
+ * Also emits the terminal capture event — without it the request would sit
233
+ * in the buffer as pending forever.
234
+ */
235
+ function _deliverMock(xhr, rule, s, bodyText, statusOverride, headersText, warning) {
236
+ var go = function () {
237
+ try {
238
+ var duration = Date.now() - s.start;
239
+ s.done = true;
240
+ if (rule.networkError) {
241
+ _setProp(xhr, 'readyState', 4);
242
+ _setProp(xhr, 'status', 0);
243
+ _setProp(xhr, 'statusText', '');
244
+ _reportAlways({ type: 'mock', id: s.id, ruleId: rule.id, warning: warning });
245
+ _report({ type: 'error', id: s.id, error: String(rule.networkError), duration: duration, mocked: true });
246
+ _fire(xhr, 'readystatechange');
247
+ _fire(xhr, 'error');
248
+ _fire(xhr, 'loadend');
249
+ return;
250
+ }
251
+ var text = (bodyText === null || bodyText === undefined) ? '' : String(bodyText);
252
+ var status = (typeof statusOverride === 'number') ? statusOverride : 200;
253
+ var text2 = headersText || '';
254
+ _setProp(xhr, 'readyState', 4);
255
+ _setProp(xhr, 'status', status);
256
+ _setProp(xhr, 'statusText', '');
257
+ _setProp(xhr, 'responseText', text);
258
+ _setProp(xhr, 'response', text);
259
+ _installMockHeaders(xhr, text2);
260
+ _reportAlways({ type: 'mock', id: s.id, ruleId: rule.id, warning: warning });
261
+ var evt = {
262
+ type: 'response', id: s.id, status: status, statusText: '',
263
+ duration: duration, responseHeaders: _parseHeaders(text2),
264
+ body: _cap(text, _RES_CAP), mocked: true
265
+ };
266
+ if (evt.responseHeaders['content-type']) evt.mimeType = evt.responseHeaders['content-type'];
267
+ _report(evt);
268
+ _fire(xhr, 'readystatechange');
269
+ _fire(xhr, 'load');
270
+ _fire(xhr, 'loadend');
271
+ } catch (e) {}
272
+ };
273
+ // Always deferred, never synchronous: a real XHR response never arrives
274
+ // inside send(), and code that assumes it does breaks in surprising ways.
275
+ var delay = (typeof rule.delayMs === 'number' && rule.delayMs > 0) ? rule.delayMs : 0;
276
+ setTimeout(go, delay);
277
+ }
278
+ function _setPath(obj, path, value) {
279
+ var parts = String(path).split('.');
280
+ var cur = obj;
281
+ for (var i = 0; i < parts.length - 1; i++) {
282
+ if (cur[parts[i]] === null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};
283
+ cur = cur[parts[i]];
284
+ }
285
+ cur[parts[parts.length - 1]] = value;
286
+ }
287
+
288
+ function _removePath(obj, path) {
289
+ var parts = String(path).split('.');
290
+ var cur = obj;
291
+ for (var i = 0; i < parts.length - 1; i++) {
292
+ if (cur === null || typeof cur !== 'object') return;
293
+ cur = cur[parts[i]];
294
+ }
295
+ if (cur && typeof cur === 'object') delete cur[parts[parts.length - 1]];
296
+ }
297
+
298
+ /**
299
+ * Returns { body, warning }. A non-JSON body is passed through untouched
300
+ * with a warning rather than replaced: handing the app a mangled body would
301
+ * turn a tamper into a parse error the agent then has to debug.
302
+ */
303
+ function _applyTamper(rule, bodyText) {
304
+ if (typeof rule.bodyReplace === 'string') return { body: rule.bodyReplace };
305
+ var wantsJson = rule.set || rule.remove;
306
+ if (!wantsJson) return { body: bodyText };
307
+ var parsed;
308
+ try {
309
+ parsed = JSON.parse(bodyText);
310
+ } catch (e) {
311
+ return { body: bodyText, warning: 'tamper skipped - response was not JSON' };
312
+ }
313
+ try {
314
+ if (rule.remove) {
315
+ for (var i = 0; i < rule.remove.length; i++) _removePath(parsed, rule.remove[i]);
316
+ }
317
+ if (rule.set) {
318
+ for (var k in rule.set) {
319
+ if (Object.prototype.hasOwnProperty.call(rule.set, k)) _setPath(parsed, k, rule.set[k]);
320
+ }
321
+ }
322
+ return { body: JSON.stringify(parsed) };
323
+ } catch (e) {
324
+ return { body: bodyText, warning: 'tamper failed: ' + String(e && e.message ? e.message : e) };
325
+ }
326
+ }
327
+ // ---- end mock layer --------------------------------------------------
328
+
329
+ function _patchXHR() {
330
+ try {
331
+ if (typeof XMLHttpRequest === 'undefined') return false;
332
+ var proto = XMLHttpRequest.prototype;
333
+ if (!proto || typeof proto.open !== 'function' || typeof proto.send !== 'function') return false;
334
+ if (proto.open.__rn_net_wrapped__) {
335
+ // Already patched (phase 2 after phase 1). The flag still has to be
336
+ // set, or the fetch wrapper would report alongside a live XHR layer.
337
+ globalThis.__RN_NET_XHR_ACTIVE__ = true;
338
+ return true;
339
+ }
340
+
341
+ var origOpen = proto.open;
342
+ var origSend = proto.send;
343
+ var origSetHeader = proto.setRequestHeader;
344
+ var _states = (typeof WeakMap === 'function') ? new WeakMap() : null;
345
+
346
+ function _getState(x) {
347
+ try { return _states ? _states.get(x) : x.__rn_net_state__; } catch(e) { return undefined; }
348
+ }
349
+ function _setState(x, s) {
350
+ try { if (_states) { _states.set(x, s); } else { x.__rn_net_state__ = s; } } catch(e) {}
351
+ }
352
+
353
+ var patchedOpen = function(method, url) {
354
+ try {
355
+ _setState(this, {
356
+ id: _genId(),
357
+ method: String(method === undefined || method === null ? 'GET' : method).toUpperCase(),
358
+ url: (typeof url === 'string') ? url : String(url),
359
+ headers: {},
360
+ start: 0,
361
+ done: false
362
+ });
363
+ } catch(e) {}
364
+ return origOpen.apply(this, arguments);
365
+ };
366
+ patchedOpen.__rn_net_wrapped__ = true;
367
+ proto.open = patchedOpen;
368
+
369
+ if (typeof origSetHeader === 'function') {
370
+ proto.setRequestHeader = function(name, value) {
371
+ try {
372
+ var s = _getState(this);
373
+ if (s) s.headers[String(name)] = String(value);
374
+ } catch(e) {}
375
+ return origSetHeader.apply(this, arguments);
376
+ };
377
+ }
378
+
379
+ // Recorded so _fire can reach the app's handlers when a mock replaces
380
+ // the response. There is no other way to enumerate them.
381
+ var origAddEventListener = proto.addEventListener;
382
+ if (typeof origAddEventListener === 'function') {
383
+ proto.addEventListener = function(type, fn) {
384
+ try {
385
+ if (!this.__rn_net_listeners__) this.__rn_net_listeners__ = {};
386
+ if (!this.__rn_net_listeners__[type]) this.__rn_net_listeners__[type] = [];
387
+ this.__rn_net_listeners__[type].push(fn);
388
+ } catch(e) {}
389
+ return origAddEventListener.apply(this, arguments);
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Runs the real request on a second XHR so the app's own request never
395
+ * reaches the wire, then hands the app a response we synthesized.
396
+ *
397
+ * This is what removes the listener-ordering hazard. Letting the app's
398
+ * request fly and mutating it in a load listener does not work: apps
399
+ * commonly assign xhr.onload BEFORE send(), and a property handler runs
400
+ * ahead of any listener added afterwards, so the app would read the
401
+ * untampered response.
402
+ *
403
+ * Lives inside _patchXHR because it must call the ORIGINAL open/send —
404
+ * a shadow that went through the patched ones would be reported as a
405
+ * second request and could match a rule itself.
406
+ */
407
+ var _shadowFetch = function(appXhr, s, rule, body) {
408
+ try {
409
+ var shadow = new XMLHttpRequest();
410
+ origOpen.call(shadow, s.method, s.url);
411
+ if (typeof origSetHeader === 'function') {
412
+ for (var name in s.headers) {
413
+ if (Object.prototype.hasOwnProperty.call(s.headers, name)) {
414
+ try { origSetHeader.call(shadow, name, s.headers[name]); } catch (e) {}
415
+ }
416
+ }
417
+ }
418
+ var done = false;
419
+ var onDone = function() {
420
+ if (done) return;
421
+ done = true;
422
+ var real = '';
423
+ try { real = shadow.responseText || ''; } catch (e) {}
424
+ var out = _applyTamper(rule, real);
425
+ var headersText = '';
426
+ try {
427
+ headersText = (typeof shadow.getAllResponseHeaders === 'function')
428
+ ? shadow.getAllResponseHeaders() : '';
429
+ } catch (e) {}
430
+ var status = (typeof rule.status === 'number') ? rule.status : shadow.status;
431
+ _deliverMock(appXhr, rule, s, out.body, status, headersText, out.warning);
432
+ };
433
+ var onFail = function() {
434
+ if (done) return;
435
+ done = true;
436
+ _deliverMock(appXhr, { id: rule.id, networkError: 'Network request failed' },
437
+ s, null, 0, '', 'shadow request failed');
438
+ };
439
+ if (typeof shadow.addEventListener === 'function') {
440
+ shadow.addEventListener('load', onDone);
441
+ shadow.addEventListener('error', onFail);
442
+ shadow.addEventListener('abort', onFail);
443
+ shadow.addEventListener('timeout', onFail);
444
+ }
445
+ origSend.call(shadow, body);
446
+ } catch (e) {
447
+ _deliverMock(appXhr, { id: rule.id, networkError: 'Network request failed' },
448
+ s, null, 0, '', 'shadow request threw');
449
+ }
450
+ };
451
+
452
+ proto.send = function(body) {
453
+ var self = this;
454
+ try {
455
+ var s = _getState(self);
456
+ if (s && !s.done && s.start === 0) {
457
+ s.start = Date.now();
458
+ var _rule = _matchRule(s.method, s.url);
459
+
460
+ _report({
461
+ type: 'request',
462
+ id: s.id,
463
+ method: s.method,
464
+ url: s.url,
465
+ timestamp: s.start,
466
+ headers: s.headers,
467
+ body: _cap(_bodyToString(body), _REQ_CAP),
468
+ // Tells the server this request will never reach the wire, so
469
+ // the CDP Network domain cannot possibly report it and the
470
+ // usual dedupe gate must not drop ours. Without it a mocked
471
+ // request is invisible on every CDP-capture target.
472
+ mocked: _rule ? true : undefined
473
+ });
474
+
475
+ if (_rule) {
476
+ s.mocked = true;
477
+ if (_rule.mode === 'tamper') {
478
+ _shadowFetch(self, s, _rule, body);
479
+ } else {
480
+ _deliverMock(self, _rule, s, _rule.body, _rule.status,
481
+ _headersToText(_rule.headers), undefined);
482
+ }
483
+ // The app's request never reaches the wire: returning here is
484
+ // what makes the mock authoritative rather than a race with
485
+ // the real response.
486
+ return;
487
+ }
488
+
489
+ var _finish = function(kind, errText) {
490
+ if (s.done) return;
491
+ s.done = true;
492
+ var duration = Date.now() - s.start;
493
+ if (kind !== 'load') {
494
+ _report({type: 'error', id: s.id, error: errText, duration: duration});
495
+ return;
496
+ }
497
+ var evt = {
498
+ type: 'response',
499
+ id: s.id,
500
+ status: self.status,
501
+ statusText: self.statusText || '',
502
+ duration: duration
503
+ };
504
+ try {
505
+ var rh = (typeof self.getAllResponseHeaders === 'function')
506
+ ? _parseHeaders(self.getAllResponseHeaders()) : {};
507
+ evt.responseHeaders = rh;
508
+ if (rh['content-type']) evt.mimeType = rh['content-type'];
509
+ if (rh['content-length']) {
510
+ var cl = Number(rh['content-length']);
511
+ if (!isNaN(cl)) evt.contentLength = cl;
512
+ }
513
+ } catch(e) {}
514
+ try {
515
+ if (self.responseURL && self.responseURL !== s.url) evt.url = self.responseURL;
516
+ } catch(e) {}
517
+ var textualBlob = false;
518
+ try {
519
+ textualBlob = self.responseType === 'blob' && _isTextual(evt.mimeType) &&
520
+ typeof FileReader !== 'undefined' && typeof Blob !== 'undefined' &&
521
+ self.response instanceof Blob;
522
+ } catch(e) {}
523
+ if (!textualBlob) evt.body = _responseBody(self);
524
+ _report(evt);
525
+ if (textualBlob) _readBlobText(s.id, self.response);
526
+ };
527
+
528
+ if (typeof self.addEventListener === 'function') {
529
+ self.addEventListener('load', function() { _finish('load'); });
530
+ self.addEventListener('error', function() { _finish('error', 'Network request failed'); });
531
+ self.addEventListener('abort', function() { _finish('error', 'Request aborted'); });
532
+ self.addEventListener('timeout', function() { _finish('error', 'Request timed out'); });
533
+ }
534
+ }
535
+ } catch(e) {}
536
+ return origSend.apply(self, arguments);
537
+ };
538
+
539
+ globalThis.__RN_NET_XHR_ACTIVE__ = true;
540
+ return true;
541
+ } catch(e) { return false; }
542
+ }
543
+
24
544
  function _wrapFetch(origFetch) {
25
545
  if (typeof origFetch !== 'function') return origFetch;
26
546
  if (origFetch.__rn_net_wrapped__) return origFetch;
27
547
 
28
548
  var wrapped = function(input, init) {
29
- var id = _genId();
549
+ // Decided once per call, not per report: if the XHR patch lands
550
+ // mid-flight, a request whose start was reported here must still
551
+ // have its response reported here, or it hangs as 'pending' forever.
552
+ var suppressed = !!globalThis.__RN_NET_XHR_ACTIVE__;
553
+ // Don't burn an id on a suppressed call — it would leave gaps in the
554
+ // sequence the XHR layer is handing out for the same requests.
555
+ var id = suppressed ? '' : _genId();
30
556
  var method = (init && init.method) ? init.method : 'GET';
31
557
  var url = '';
32
558
  if (typeof input === 'string') {
@@ -38,29 +564,37 @@ export function getInterceptorScript() {
38
564
  }
39
565
  var startTime = Date.now();
40
566
 
41
- _report({type: 'request', id: id, method: method, url: url, timestamp: startTime});
567
+ if (!suppressed) {
568
+ _report({type: 'request', id: id, method: method, url: url, timestamp: startTime});
569
+ }
42
570
 
43
571
  try {
44
572
  return origFetch.apply(globalThis, arguments).then(
45
573
  function(response) {
46
574
  try {
47
- var duration = Date.now() - startTime;
48
- _report({type: 'response', id: id, status: response.status, statusText: response.statusText || '', duration: duration});
575
+ if (!suppressed) {
576
+ var duration = Date.now() - startTime;
577
+ _report({type: 'response', id: id, status: response.status, statusText: response.statusText || '', duration: duration});
578
+ }
49
579
  } catch(e) {}
50
580
  return response;
51
581
  },
52
582
  function(err) {
53
583
  try {
54
- var duration = Date.now() - startTime;
55
- _report({type: 'error', id: id, error: (err && err.message) ? err.message : 'Fetch failed', duration: duration});
584
+ if (!suppressed) {
585
+ var duration = Date.now() - startTime;
586
+ _report({type: 'error', id: id, error: (err && err.message) ? err.message : 'Fetch failed', duration: duration});
587
+ }
56
588
  } catch(e) {}
57
589
  throw err;
58
590
  }
59
591
  );
60
592
  } catch(e) {
61
593
  try {
62
- var duration = Date.now() - startTime;
63
- _report({type: 'error', id: id, error: (e && e.message) ? e.message : 'Fetch failed', duration: duration});
594
+ if (!suppressed) {
595
+ var duration = Date.now() - startTime;
596
+ _report({type: 'error', id: id, error: (e && e.message) ? e.message : 'Fetch failed', duration: duration});
597
+ }
64
598
  } catch(e2) {}
65
599
  throw e;
66
600
  }
@@ -69,7 +603,9 @@ export function getInterceptorScript() {
69
603
  return wrapped;
70
604
  }
71
605
 
72
- // Phase 1: try to trap fetch assignment via defineProperty (best-effort)
606
+ // Phase 1: patch XHR (axios and RN's own fetch polyfill both ride on it),
607
+ // then try to trap fetch assignment via defineProperty (best-effort)
608
+ _patchXHR();
73
609
  try {
74
610
  if (typeof globalThis.fetch === 'function') {
75
611
  globalThis.fetch = _wrapFetch(globalThis.fetch);
@@ -83,17 +619,58 @@ export function getInterceptorScript() {
83
619
  });
84
620
  } catch(e) {}
85
621
 
86
- // Phase 2: wrap fetch after module init completes (reliable fallback)
87
- setTimeout(function() {
622
+ // Phase 2: retry XHR and wrap fetch after module init completes.
623
+ //
624
+ // Bounded retry rather than a single tick. On a cold app launch the CDP
625
+ // connect can land before the JS bundle has defined XMLHttpRequest, and a
626
+ // one-shot retry loses that race silently: __RN_NET_XHR_ACTIVE__ stays
627
+ // false, capture and mocking are dead for the whole run, and because
628
+ // __RN_NET_INJECTED__ is already set no later injection can repair it.
629
+ // Observed on a freshly launched Android emulator while iOS, connected
630
+ // earlier, patched fine — the failure is pure timing and leaves no trace.
631
+ var _retries = 0;
632
+ var _retry = function() {
633
+ var patched = false;
634
+ try { patched = _patchXHR(); } catch(e) {}
88
635
  try {
89
636
  if (typeof globalThis.fetch === 'function' && !globalThis.fetch.__rn_net_wrapped__) {
90
637
  globalThis.fetch = _wrapFetch(globalThis.fetch);
91
638
  }
92
639
  } catch(e) {}
93
- }, 0);
640
+ if (patched) return;
641
+ _retries++;
642
+ if (_retries >= 40) return; // ~10s, then give up rather than spin forever
643
+ try { setTimeout(_retry, 250); } catch(e) {}
644
+ };
645
+ setTimeout(_retry, 0);
94
646
 
95
647
  } catch(e) {} })();`;
96
648
  }
649
+ export function buildMockPushScript(rulesJson) {
650
+ return `(function(){ try {
651
+ var __eb_prev = globalThis.__RN_NET_MOCKS__ || [];
652
+ var __eb_next = ${rulesJson};
653
+ var __eb_byId = {};
654
+ for (var __eb_i = 0; __eb_i < __eb_prev.length; __eb_i++) {
655
+ if (__eb_prev[__eb_i] && __eb_prev[__eb_i].id) __eb_byId[__eb_prev[__eb_i].id] = __eb_prev[__eb_i];
656
+ }
657
+ for (var __eb_j = 0; __eb_j < __eb_next.length; __eb_j++) {
658
+ var __eb_old = __eb_byId[__eb_next[__eb_j].id];
659
+ if (__eb_old) {
660
+ __eb_next[__eb_j].__used = __eb_old.__used;
661
+ __eb_next[__eb_j].__spent = __eb_old.__spent;
662
+ }
663
+ }
664
+ globalThis.__RN_NET_MOCKS__ = __eb_next;
665
+ } catch(e) { try { globalThis.__RN_NET_MOCKS__ = ${rulesJson}; } catch(e2) {} } })();`;
666
+ }
667
+ export function pushMockRules(ws, rulesJson) {
668
+ ws.send(JSON.stringify({
669
+ id: getNextMessageId(),
670
+ method: "Runtime.evaluate",
671
+ params: { expression: buildMockPushScript(rulesJson), silent: true },
672
+ }));
673
+ }
97
674
  export function injectNetworkInterceptor(ws) {
98
675
  const message = JSON.stringify({
99
676
  id: getNextMessageId(),
@@ -126,6 +703,38 @@ export function isInterceptorEvent(args) {
126
703
  return null;
127
704
  return val.slice(prefix.length);
128
705
  }
706
+ export function isMockEvent(jsonStr) {
707
+ try {
708
+ const parsed = JSON.parse(jsonStr);
709
+ return parsed?.type === "mock";
710
+ }
711
+ catch {
712
+ return false;
713
+ }
714
+ }
715
+ export function isMockedTrafficEvent(jsonStr) {
716
+ try {
717
+ const parsed = JSON.parse(jsonStr);
718
+ return parsed?.mocked === true;
719
+ }
720
+ catch {
721
+ return false;
722
+ }
723
+ }
724
+ function toStringRecord(value) {
725
+ const out = {};
726
+ if (!value || typeof value !== "object" || Array.isArray(value))
727
+ return out;
728
+ for (const [key, val] of Object.entries(value)) {
729
+ if (typeof val === "string") {
730
+ out[key] = val;
731
+ }
732
+ else if (typeof val === "number" || typeof val === "boolean") {
733
+ out[key] = String(val);
734
+ }
735
+ }
736
+ return out;
737
+ }
129
738
  export function applyInterceptedEvent(jsonStr, networkBuffer, deviceName) {
130
739
  let event;
131
740
  try {
@@ -147,7 +756,8 @@ export function applyInterceptedEvent(jsonStr, networkBuffer, deviceName) {
147
756
  : new Date(),
148
757
  method: String(event.method || "GET"),
149
758
  url: String(event.url || ""),
150
- headers: {},
759
+ headers: toStringRecord(event.headers),
760
+ postData: typeof event.body === "string" ? event.body : undefined,
151
761
  completed: false,
152
762
  epoch: getEpoch(deviceName),
153
763
  };
@@ -167,8 +777,45 @@ export function applyInterceptedEvent(jsonStr, networkBuffer, deviceName) {
167
777
  responseTime: Date.now(),
168
778
  duration,
169
779
  };
780
+ if (event.responseHeaders !== undefined) {
781
+ existing.responseHeaders = toStringRecord(event.responseHeaders);
782
+ }
783
+ if (typeof event.mimeType === "string") {
784
+ existing.mimeType = event.mimeType;
785
+ }
786
+ if (typeof event.contentLength === "number" && Number.isFinite(event.contentLength)) {
787
+ existing.contentLength = event.contentLength;
788
+ }
789
+ if (typeof event.body === "string") {
790
+ existing.responseBody = event.body;
791
+ }
792
+ if (typeof event.url === "string" && event.url.length > 0) {
793
+ existing.url = event.url;
794
+ }
170
795
  networkBuffer.set(id, existing);
171
796
  }
797
+ else if (type === "mock") {
798
+ if (typeof event.ruleId === "string")
799
+ recordHit(deviceName, event.ruleId);
800
+ const existing = networkBuffer.get(id);
801
+ if (!existing)
802
+ return;
803
+ existing.mocked = true;
804
+ if (typeof event.ruleId === "string")
805
+ existing.mockId = event.ruleId;
806
+ if (typeof event.warning === "string")
807
+ existing.mockWarning = event.warning;
808
+ networkBuffer.set(id, existing);
809
+ }
810
+ else if (type === "body") {
811
+ const existing = networkBuffer.get(id);
812
+ if (!existing)
813
+ return;
814
+ if (typeof event.body === "string") {
815
+ existing.responseBody = event.body;
816
+ networkBuffer.set(id, existing);
817
+ }
818
+ }
172
819
  else if (type === "error") {
173
820
  const existing = networkBuffer.get(id);
174
821
  if (!existing)