@buoy-gg/network 7.0.41 → 7.0.43

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 (272) hide show
  1. package/README.md +11 -85
  2. package/lib/commonjs/index.js +1 -332
  3. package/lib/commonjs/index.web.js +1 -420
  4. package/lib/commonjs/network/components/DataViewer.web.js +1 -879
  5. package/lib/commonjs/network/components/NetworkCopyScreen.web.js +1 -48
  6. package/lib/commonjs/network/components/NetworkCopySettingsView.js +27 -755
  7. package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +14 -1242
  8. package/lib/commonjs/network/components/NetworkDetailScreen.web.js +1 -101
  9. package/lib/commonjs/network/components/NetworkDetailStepper.js +1 -108
  10. package/lib/commonjs/network/components/NetworkDetailStepper.web.js +1 -298
  11. package/lib/commonjs/network/components/NetworkEventDetailView.js +15 -1130
  12. package/lib/commonjs/network/components/NetworkEventDetailView.web.js +15 -1484
  13. package/lib/commonjs/network/components/NetworkEventItemCompact.js +2 -401
  14. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +2 -820
  15. package/lib/commonjs/network/components/NetworkFilterScreen.web.js +1 -55
  16. package/lib/commonjs/network/components/NetworkFilterViewV3.js +1 -442
  17. package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1 -1310
  18. package/lib/commonjs/network/components/NetworkHeaderLive.js +1 -375
  19. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +1 -623
  20. package/lib/commonjs/network/components/NetworkHeaderMenu.js +1 -190
  21. package/lib/commonjs/network/components/NetworkListScreen.js +1 -403
  22. package/lib/commonjs/network/components/NetworkListScreen.web.js +1 -624
  23. package/lib/commonjs/network/components/NetworkModal.js +1 -786
  24. package/lib/commonjs/network/components/NetworkNumberPrompt.js +1 -178
  25. package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +1 -232
  26. package/lib/commonjs/network/components/NetworkOverrideEditor.js +1 -814
  27. package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +1 -942
  28. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +1 -107
  29. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +1 -133
  30. package/lib/commonjs/network/components/NetworkOverridesButton.js +1 -90
  31. package/lib/commonjs/network/components/NetworkOverridesButton.web.js +1 -143
  32. package/lib/commonjs/network/components/NetworkOverridesScreen.js +1 -543
  33. package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +1 -830
  34. package/lib/commonjs/network/components/NetworkSavedScreen.js +1 -226
  35. package/lib/commonjs/network/components/NetworkSavedScreen.web.js +1 -463
  36. package/lib/commonjs/network/components/NetworkUpgradeBanner.js +1 -76
  37. package/lib/commonjs/network/components/response-editor/Explorer.js +1 -973
  38. package/lib/commonjs/network/components/response-editor/Explorer.web.js +1 -1136
  39. package/lib/commonjs/network/components/response-editor/deleteNestedDataByPath.js +1 -56
  40. package/lib/commonjs/network/components/response-editor/editorContext.js +1 -33
  41. package/lib/commonjs/network/components/response-editor/updateNestedDataByPath.js +1 -61
  42. package/lib/commonjs/network/conditions/NetworkConditionsControl.js +1 -235
  43. package/lib/commonjs/network/conditions/NetworkConditionsControl.test.js +1 -136
  44. package/lib/commonjs/network/conditions/NetworkConditionsIcon.js +1 -33
  45. package/lib/commonjs/network/conditions/NetworkFloatingTool.js +1 -310
  46. package/lib/commonjs/network/conditions/NetworkFloatingTool.test.js +1 -219
  47. package/lib/commonjs/network/conditions/store.js +1 -75
  48. package/lib/commonjs/network/conditions/wait.js +1 -43
  49. package/lib/commonjs/network/conditions/xhr.js +1 -135
  50. package/lib/commonjs/network/hooks/useLiveNetworkEvent.js +1 -62
  51. package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +1 -83
  52. package/lib/commonjs/network/hooks/useNetworkEventList.js +1 -108
  53. package/lib/commonjs/network/hooks/useNetworkEvents.js +1 -318
  54. package/lib/commonjs/network/hooks/useNetworkHasMatches.js +1 -56
  55. package/lib/commonjs/network/hooks/useNetworkSaved.js +1 -38
  56. package/lib/commonjs/network/hooks/useNetworkSelection.js +1 -53
  57. package/lib/commonjs/network/hooks/useOverrideRules.js +1 -36
  58. package/lib/commonjs/network/hooks/useTickEveryMinute.js +1 -21
  59. package/lib/commonjs/network/overrides/applyToFetch.js +1 -80
  60. package/lib/commonjs/network/overrides/applyToXhr.js +2 -510
  61. package/lib/commonjs/network/overrides/engine.js +1 -93
  62. package/lib/commonjs/network/overrides/matchRule.js +1 -87
  63. package/lib/commonjs/network/overrides/overriddenRows.js +1 -37
  64. package/lib/commonjs/network/overrides/overrideRulesStore.js +1 -550
  65. package/lib/commonjs/network/overrides/presets.js +1 -70
  66. package/lib/commonjs/network/overrides/resolveOverride.js +1 -158
  67. package/lib/commonjs/network/overrides/types.js +1 -90
  68. package/lib/commonjs/network/sync/networkBodyResolver.js +1 -39
  69. package/lib/commonjs/network/sync/networkSyncAdapter.js +1 -882
  70. package/lib/commonjs/network/sync/overrideHonesty.release.test.js +1 -36
  71. package/lib/commonjs/network/types/index.js +1 -5
  72. package/lib/commonjs/network/utils/extractOperationName.js +1 -80
  73. package/lib/commonjs/network/utils/filterNetworkEvents.js +1 -96
  74. package/lib/commonjs/network/utils/formatGraphQLVariables.js +1 -219
  75. package/lib/commonjs/network/utils/formatting.js +1 -30
  76. package/lib/commonjs/network/utils/generateCurl.js +2 -71
  77. package/lib/commonjs/network/utils/generateNetworkCopyText.js +37 -166
  78. package/lib/commonjs/network/utils/httpCompletion.js +1 -37
  79. package/lib/commonjs/network/utils/networkEventStore.js +1 -411
  80. package/lib/commonjs/network/utils/networkListener.js +4 -1352
  81. package/lib/commonjs/network/utils/networkSavedStore.js +3 -577
  82. package/lib/commonjs/network/utils/networkSelectionStore.js +1 -193
  83. package/lib/commonjs/network/utils/realNow.js +1 -0
  84. package/lib/commonjs/network/utils/requestId.js +1 -16
  85. package/lib/commonjs/network/utils/resolveRequestUrl.js +1 -15
  86. package/lib/commonjs/network/utils/selectSavedEvents.js +1 -39
  87. package/lib/commonjs/network/utils/startNetworkCapture.js +1 -24
  88. package/lib/commonjs/network/utils/withPinnedFirst.js +1 -32
  89. package/lib/commonjs/preset.js +1 -93
  90. package/lib/module/index.js +1 -104
  91. package/lib/module/index.web.js +1 -82
  92. package/lib/module/network/components/DataViewer.web.js +1 -875
  93. package/lib/module/network/components/NetworkCopyScreen.web.js +1 -44
  94. package/lib/module/network/components/NetworkCopySettingsView.js +27 -750
  95. package/lib/module/network/components/NetworkCopySettingsView.web.js +14 -1237
  96. package/lib/module/network/components/NetworkDetailScreen.web.js +1 -97
  97. package/lib/module/network/components/NetworkDetailStepper.js +1 -104
  98. package/lib/module/network/components/NetworkDetailStepper.web.js +1 -296
  99. package/lib/module/network/components/NetworkEventDetailView.js +15 -1129
  100. package/lib/module/network/components/NetworkEventDetailView.web.js +15 -1482
  101. package/lib/module/network/components/NetworkEventItemCompact.js +2 -398
  102. package/lib/module/network/components/NetworkEventItemCompact.web.js +2 -818
  103. package/lib/module/network/components/NetworkFilterScreen.web.js +1 -51
  104. package/lib/module/network/components/NetworkFilterViewV3.js +1 -438
  105. package/lib/module/network/components/NetworkFilterViewV3.web.js +1 -1306
  106. package/lib/module/network/components/NetworkHeaderLive.js +1 -366
  107. package/lib/module/network/components/NetworkHeaderLive.web.js +1 -617
  108. package/lib/module/network/components/NetworkHeaderMenu.js +1 -184
  109. package/lib/module/network/components/NetworkListScreen.js +1 -399
  110. package/lib/module/network/components/NetworkListScreen.web.js +1 -622
  111. package/lib/module/network/components/NetworkModal.js +1 -781
  112. package/lib/module/network/components/NetworkNumberPrompt.js +1 -174
  113. package/lib/module/network/components/NetworkNumberPrompt.web.js +1 -230
  114. package/lib/module/network/components/NetworkOverrideEditor.js +1 -811
  115. package/lib/module/network/components/NetworkOverrideEditor.web.js +1 -939
  116. package/lib/module/network/components/NetworkOverrideHeaderButton.js +1 -104
  117. package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +1 -131
  118. package/lib/module/network/components/NetworkOverridesButton.js +1 -87
  119. package/lib/module/network/components/NetworkOverridesButton.web.js +1 -141
  120. package/lib/module/network/components/NetworkOverridesScreen.js +1 -539
  121. package/lib/module/network/components/NetworkOverridesScreen.web.js +1 -826
  122. package/lib/module/network/components/NetworkSavedScreen.js +1 -222
  123. package/lib/module/network/components/NetworkSavedScreen.web.js +1 -461
  124. package/lib/module/network/components/NetworkUpgradeBanner.js +1 -72
  125. package/lib/module/network/components/response-editor/Explorer.js +1 -972
  126. package/lib/module/network/components/response-editor/Explorer.web.js +1 -1133
  127. package/lib/module/network/components/response-editor/deleteNestedDataByPath.js +1 -51
  128. package/lib/module/network/components/response-editor/editorContext.js +1 -29
  129. package/lib/module/network/components/response-editor/updateNestedDataByPath.js +1 -56
  130. package/lib/module/network/conditions/NetworkConditionsControl.js +1 -231
  131. package/lib/module/network/conditions/NetworkConditionsControl.test.js +1 -135
  132. package/lib/module/network/conditions/NetworkConditionsIcon.js +1 -29
  133. package/lib/module/network/conditions/NetworkFloatingTool.js +1 -304
  134. package/lib/module/network/conditions/NetworkFloatingTool.test.js +1 -218
  135. package/lib/module/network/conditions/store.js +1 -65
  136. package/lib/module/network/conditions/wait.js +1 -38
  137. package/lib/module/network/conditions/xhr.js +1 -131
  138. package/lib/module/network/hooks/useLiveNetworkEvent.js +1 -58
  139. package/lib/module/network/hooks/useNetworkBadgeCounts.js +1 -79
  140. package/lib/module/network/hooks/useNetworkEventList.js +1 -104
  141. package/lib/module/network/hooks/useNetworkEvents.js +1 -314
  142. package/lib/module/network/hooks/useNetworkHasMatches.js +1 -52
  143. package/lib/module/network/hooks/useNetworkSaved.js +1 -34
  144. package/lib/module/network/hooks/useNetworkSelection.js +1 -46
  145. package/lib/module/network/hooks/useOverrideRules.js +1 -32
  146. package/lib/module/network/hooks/useTickEveryMinute.js +1 -11
  147. package/lib/module/network/overrides/applyToFetch.js +1 -72
  148. package/lib/module/network/overrides/applyToXhr.js +2 -502
  149. package/lib/module/network/overrides/engine.js +1 -85
  150. package/lib/module/network/overrides/matchRule.js +1 -81
  151. package/lib/module/network/overrides/overriddenRows.js +1 -33
  152. package/lib/module/network/overrides/overrideRulesStore.js +1 -547
  153. package/lib/module/network/overrides/presets.js +1 -65
  154. package/lib/module/network/overrides/resolveOverride.js +1 -152
  155. package/lib/module/network/overrides/types.js +1 -86
  156. package/lib/module/network/sync/networkBodyResolver.js +1 -34
  157. package/lib/module/network/sync/networkSyncAdapter.js +1 -880
  158. package/lib/module/network/sync/overrideHonesty.release.test.js +1 -35
  159. package/lib/module/network/types/index.js +1 -3
  160. package/lib/module/network/utils/extractOperationName.js +1 -76
  161. package/lib/module/network/utils/filterNetworkEvents.js +1 -92
  162. package/lib/module/network/utils/formatGraphQLVariables.js +1 -213
  163. package/lib/module/network/utils/formatting.js +1 -9
  164. package/lib/module/network/utils/generateCurl.js +2 -67
  165. package/lib/module/network/utils/generateNetworkCopyText.js +37 -162
  166. package/lib/module/network/utils/httpCompletion.js +1 -32
  167. package/lib/module/network/utils/networkEventStore.js +1 -409
  168. package/lib/module/network/utils/networkListener.js +4 -1341
  169. package/lib/module/network/utils/networkSavedStore.js +3 -572
  170. package/lib/module/network/utils/networkSelectionStore.js +1 -188
  171. package/lib/module/network/utils/realNow.js +1 -0
  172. package/lib/module/network/utils/requestId.js +1 -12
  173. package/lib/module/network/utils/resolveRequestUrl.js +1 -11
  174. package/lib/module/network/utils/selectSavedEvents.js +1 -35
  175. package/lib/module/network/utils/startNetworkCapture.js +1 -21
  176. package/lib/module/network/utils/withPinnedFirst.js +1 -28
  177. package/lib/module/preset.js +1 -91
  178. package/lib/typescript/network/overrides/engine.d.ts +1 -1
  179. package/lib/typescript/network/overrides/matchRule.d.ts +1 -1
  180. package/lib/typescript/network/overrides/resolveOverride.d.ts +2 -2
  181. package/lib/typescript/network/overrides/types.d.ts +9 -0
  182. package/lib/typescript/network/utils/networkListener.d.ts +11 -0
  183. package/lib/typescript/network/utils/realNow.d.ts +18 -0
  184. package/lib/web/index.mjs +73 -74
  185. package/package.json +3 -6
  186. package/lib/typescript/index.d.ts.map +0 -1
  187. package/lib/typescript/index.web.d.ts.map +0 -1
  188. package/lib/typescript/network/components/DataViewer.web.d.ts.map +0 -1
  189. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +0 -1
  190. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +0 -1
  191. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +0 -1
  192. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +0 -1
  193. package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +0 -1
  194. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +0 -1
  195. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +0 -1
  196. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +0 -1
  197. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +0 -1
  198. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +0 -1
  199. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +0 -1
  200. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +0 -1
  201. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +0 -1
  202. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +0 -1
  203. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +0 -1
  204. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +0 -1
  205. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +0 -1
  206. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +0 -1
  207. package/lib/typescript/network/components/NetworkModal.d.ts.map +0 -1
  208. package/lib/typescript/network/components/NetworkNumberPrompt.d.ts.map +0 -1
  209. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +0 -1
  210. package/lib/typescript/network/components/NetworkOverrideEditor.d.ts.map +0 -1
  211. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +0 -1
  212. package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts.map +0 -1
  213. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +0 -1
  214. package/lib/typescript/network/components/NetworkOverridesButton.d.ts.map +0 -1
  215. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +0 -1
  216. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +0 -1
  217. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +0 -1
  218. package/lib/typescript/network/components/NetworkSavedScreen.d.ts.map +0 -1
  219. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +0 -1
  220. package/lib/typescript/network/components/NetworkUpgradeBanner.d.ts.map +0 -1
  221. package/lib/typescript/network/components/response-editor/Explorer.d.ts.map +0 -1
  222. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +0 -1
  223. package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts.map +0 -1
  224. package/lib/typescript/network/components/response-editor/editorContext.d.ts.map +0 -1
  225. package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts.map +0 -1
  226. package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts.map +0 -1
  227. package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts.map +0 -1
  228. package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts.map +0 -1
  229. package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts.map +0 -1
  230. package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts.map +0 -1
  231. package/lib/typescript/network/conditions/store.d.ts.map +0 -1
  232. package/lib/typescript/network/conditions/wait.d.ts.map +0 -1
  233. package/lib/typescript/network/conditions/xhr.d.ts.map +0 -1
  234. package/lib/typescript/network/hooks/useLiveNetworkEvent.d.ts.map +0 -1
  235. package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +0 -1
  236. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +0 -1
  237. package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +0 -1
  238. package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +0 -1
  239. package/lib/typescript/network/hooks/useNetworkSaved.d.ts.map +0 -1
  240. package/lib/typescript/network/hooks/useNetworkSelection.d.ts.map +0 -1
  241. package/lib/typescript/network/hooks/useOverrideRules.d.ts.map +0 -1
  242. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +0 -1
  243. package/lib/typescript/network/overrides/applyToFetch.d.ts.map +0 -1
  244. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +0 -1
  245. package/lib/typescript/network/overrides/engine.d.ts.map +0 -1
  246. package/lib/typescript/network/overrides/matchRule.d.ts.map +0 -1
  247. package/lib/typescript/network/overrides/overriddenRows.d.ts.map +0 -1
  248. package/lib/typescript/network/overrides/overrideRulesStore.d.ts.map +0 -1
  249. package/lib/typescript/network/overrides/presets.d.ts.map +0 -1
  250. package/lib/typescript/network/overrides/resolveOverride.d.ts.map +0 -1
  251. package/lib/typescript/network/overrides/types.d.ts.map +0 -1
  252. package/lib/typescript/network/sync/networkBodyResolver.d.ts.map +0 -1
  253. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +0 -1
  254. package/lib/typescript/network/sync/overrideHonesty.release.test.d.ts.map +0 -1
  255. package/lib/typescript/network/types/index.d.ts.map +0 -1
  256. package/lib/typescript/network/utils/extractOperationName.d.ts.map +0 -1
  257. package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +0 -1
  258. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +0 -1
  259. package/lib/typescript/network/utils/formatting.d.ts.map +0 -1
  260. package/lib/typescript/network/utils/generateCurl.d.ts.map +0 -1
  261. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +0 -1
  262. package/lib/typescript/network/utils/httpCompletion.d.ts.map +0 -1
  263. package/lib/typescript/network/utils/networkEventStore.d.ts.map +0 -1
  264. package/lib/typescript/network/utils/networkListener.d.ts.map +0 -1
  265. package/lib/typescript/network/utils/networkSavedStore.d.ts.map +0 -1
  266. package/lib/typescript/network/utils/networkSelectionStore.d.ts.map +0 -1
  267. package/lib/typescript/network/utils/requestId.d.ts.map +0 -1
  268. package/lib/typescript/network/utils/resolveRequestUrl.d.ts.map +0 -1
  269. package/lib/typescript/network/utils/selectSavedEvents.d.ts.map +0 -1
  270. package/lib/typescript/network/utils/startNetworkCapture.d.ts.map +0 -1
  271. package/lib/typescript/network/utils/withPinnedFirst.d.ts.map +0 -1
  272. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,1344 +1,7 @@
1
- "use strict";
1
+ "use strict";import{resolveRequestUrl as U}from"./resolveRequestUrl";import{nextRequestId as B}from"./requestId";import{runConditionXhr as Y}from"../conditions/xhr";import{snapshotNetworkCondition as z}from"../conditions/store";import{throwIfAborted as G,waitBeforeDispatch as K}from"../conditions/wait";import{overrideForRequest as j}from"../overrides/engine";import{runOverride as V,hasPendingStandardRequest as W}from"../overrides/applyToXhr";import{canSynthesizeResponse as Q,synthesizeFetchError as Z,synthesizeResponse as ee}from"../overrides/applyToFetch";import{markForOutcome as J}from"../overrides/types";import{httpStatusText as te,xhrCompletionKind as se}from"./httpCompletion";import{realNow as _}from"./realNow";let O;function $(b){const e=globalThis.__buoyHeaderInjectors;if(!e||e.length===0)return void 0;let t;for(const s of e){try{const c=s(b);if(c)t={...t,...c}}catch{}}return t}function ne(){if(O!==void 0)return O;O=null;try{if(typeof __DEV__!=="undefined"&&!__DEV__)return O;const b=require("react-native");const e=b?.NativeModules?.SourceCode;const t=e?.getConstants?.().scriptURL??e?.scriptURL;if(typeof t==="string"&&/^https?:\/\//.test(t)){const s=new URL(t);O=`${s.protocol}//${s.host}`}}catch{O=null}return O}let D=false;function re(b){const e={};if(!b)return e;if(typeof Headers!=="undefined"&&b instanceof Headers)b.forEach((t,s)=>{e[s]=t});else if(Array.isArray(b))for(const[t,s]of b)e[t]=s;else Object.assign(e,b);return e}export const BUOY_FETCH_INTERCEPTOR_FLAG="__buoyNetworkFetchIntercepted";export const BUOY_XHR_INTERCEPTOR_FLAG="__buoyNetworkXhrIntercepted";const oe=["text/event-stream","application/x-ndjson","application/stream+json"];const P="[streaming\u2026]";function ie(b){if(!b)return false;const e=b.toLowerCase();return oe.some(t=>e.includes(t))}const ae="__BUOY_INTERNAL_HOSTS__";function ce(b){const e=globalThis[ae];if(!(e instanceof Set)||e.size===0)return false;let t;try{t=new URL(b).host.toLowerCase()}catch{return false}for(const s of e){if(typeof s==="string"&&s&&t===s.toLowerCase())return true}return false}class de{listeners=[];isListening=false;patchedFetch=null;patchedXHROpen=null;patchedXHRSend=null;patchedXHRSetRequestHeader=null;fetchGeneration=0;xhrGeneration=0;constructor(){this.originalFetch=globalThis.fetch;this.originalXHROpen=XMLHttpRequest.prototype.open;this.originalXHRSend=XMLHttpRequest.prototype.send;this.originalXHRSetRequestHeader=XMLHttpRequest.prototype.setRequestHeader}shouldIgnoreUrl(e){try{const t=new URL(e);if(t.hostname==="keygen.sh"||t.hostname.endsWith(".keygen.sh"))return true;if((t.hostname==="buoy.gg"||t.hostname==="www.buoy.gg")&&t.pathname.startsWith("/api/license"))return true;const s=t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="10.0.2.2";if(s&&["8081","8802"].includes(t.port))return true;const c=ne();if(c&&`${t.protocol}//${t.host}`===c)return true}catch{return false}return ce(e)}emit(e){this.listeners.forEach(t=>{try{t(e)}catch(s){}})}parseUrl(e){let t=null;const s=e.indexOf("?");if(s>-1){t={};e.substring(s+1).split("&").forEach(c=>{const[a,r]=c.split("=");if(a&&r!==void 0){t[a]=decodeURIComponent(r.replace(/\+/g," "))}})}return{url:s>-1?e.substring(0,s):e,params:t}}async readStreamedBody(e,t=256*1024){const s=e.body;if(!s||typeof s.getReader!=="function"){const f=await e.text();return f.length>t?{body:`${f.slice(0,t)}
2
2
 
3
- import { resolveRequestUrl } from "./resolveRequestUrl";
4
- import { nextRequestId } from "./requestId";
5
- import { runConditionXhr } from "../conditions/xhr";
6
- import { snapshotNetworkCondition } from "../conditions/store";
7
- import { throwIfAborted, waitBeforeDispatch } from "../conditions/wait";
8
- /**
9
- * Network listener using Reactotron-style event pattern
10
- * Simple and reliable network interception for React Native
11
- */
3
+ ... [truncated]`,size:f.length}:{body:f,size:f.length}}const c=s.getReader();const a=new TextDecoder;let r="";let u=0;let g=false;try{for(;;){const{done:f,value:i}=await c.read();if(f)break;u+=i?.byteLength??0;if(!g){r+=a.decode(i,{stream:true});if(u>=t){r=`${r.slice(0,t)}
12
4
 
13
- import { overrideForRequest } from "../overrides/engine";
14
- import { runOverride, hasPendingStandardRequest } from "../overrides/applyToXhr";
15
- import { canSynthesizeResponse, synthesizeFetchError, synthesizeResponse } from "../overrides/applyToFetch";
16
- import { markForOutcome } from "../overrides/types";
17
- import { httpStatusText, xhrCompletionKind } from "./httpCompletion";
18
- let devOrigin;
5
+ ... [truncated]`;g=true;break}}}if(!g)r+=a.decode()}finally{try{void Promise.resolve(c.cancel()).catch(()=>{})}catch{}try{c.releaseLock()}catch{}}return{body:r,size:u}}async processResponseBody(e,t=1024*1024){try{const s=e.headers.get("content-length");if(s){const r=parseInt(s,10);if(!isNaN(r)&&r>t){return{body:`[Response too large: ${this.formatBytes(r)}]`,size:r,truncated:true}}}const c=await e.text();const a=c.length;if(a>t){const r=c.substring(0,t);const u=a-t;return{body:`${r}
19
6
 
20
- /**
21
- * Headers another Buoy tool adds to a request inside our wrapper.
22
- *
23
- * Impersonate patches fetch/XHR at boot, so this listener usually wraps it
24
- * and records the request before the impersonation header is added. The
25
- * request on the wire carried the header and the Network row did not. Tools
26
- * that add headers register a function here, keyed by URL, so the row shows
27
- * what was actually sent.
28
- */
29
- function injectedHeaders(url) {
30
- const injectors = globalThis.__buoyHeaderInjectors;
31
- if (!injectors || injectors.length === 0) return undefined;
32
- let out;
33
- for (const inject of injectors) {
34
- try {
35
- const h = inject(url);
36
- if (h) out = {
37
- ...out,
38
- ...h
39
- };
40
- } catch {
41
- /* a broken injector must not break capture */
42
- }
43
- }
44
- return out;
45
- }
46
-
47
- /**
48
- * Origin of the Metro dev server this JS bundle was loaded from, read once from
49
- * the bundle URL React Native reports. `null` in release builds (the bundle is
50
- * a file) and anywhere react-native isn't present.
51
- */
52
- function devServerOrigin() {
53
- if (devOrigin !== undefined) return devOrigin;
54
- devOrigin = null;
55
- try {
56
- if (typeof __DEV__ !== "undefined" && !__DEV__) return devOrigin;
57
- // eslint-disable-next-line @typescript-eslint/no-require-imports
58
- const rn = require("react-native");
59
- const sc = rn?.NativeModules?.SourceCode;
60
- const url = sc?.getConstants?.().scriptURL ?? sc?.scriptURL;
61
- if (typeof url === "string" && /^https?:\/\//.test(url)) {
62
- const u = new URL(url);
63
- devOrigin = `${u.protocol}//${u.host}`;
64
- }
65
- } catch {
66
- devOrigin = null;
67
- }
68
- return devOrigin;
69
- }
70
-
71
- /**
72
- * True only while `originalFetch` is being CALLED (not awaited).
73
- *
74
- * Overrides hook both `fetch` and `XMLHttpRequest`, because Expo's native
75
- * `expo/fetch` bypasses XHR while bare RN's whatwg-fetch polyfill runs straight
76
- * through it. On bare RN that means one `fetch()` would otherwise be overridden
77
- * twice — a doubled delay and a doubled hit count.
78
- *
79
- * whatwg-fetch creates and sends its XHR synchronously inside the Promise
80
- * executor, so a flag raised across the call expression alone is enough to
81
- * cover the inner `send()` without leaking across the await.
82
- */
83
- let insideFetchHandoff = false;
84
-
85
- // Extended XMLHttpRequest interface for monkey-patching
86
-
87
- // Event types for network operations
88
-
89
- /**
90
- * Markers stamped on the installed interceptor functions so their presence in
91
- * the live chain stays detectable even after another tool wraps them.
92
- * Cooperating patchers (@buoy-gg/impersonate) copy every `__buoy*` marker from
93
- * the function they wrap onto their own wrapper; a global fetch/XHR WITHOUT
94
- * the marker therefore means our interceptor was clobbered — some runtime
95
- * re-assigned `globalThis.fetch` over it (Expo's winter runtime, React Native
96
- * DevTools' network panel, a second dev server's tooling, hot-reload churn all
97
- * do this in the wild) — and capture must re-install. See startListening.
98
- */
99
- export const BUOY_FETCH_INTERCEPTOR_FLAG = "__buoyNetworkFetchIntercepted";
100
- export const BUOY_XHR_INTERCEPTOR_FLAG = "__buoyNetworkXhrIntercepted";
101
- /**
102
- * Network traffic interceptor for React Native applications
103
- *
104
- * This class intercepts both fetch and XMLHttpRequest operations to provide
105
- * comprehensive network monitoring capabilities. It uses method swizzling to
106
- * wrap native networking APIs while preserving their original functionality.
107
- *
108
- * @example
109
- * ```typescript
110
- * // Start monitoring network traffic
111
- * startNetworkListener();
112
- *
113
- * // Add a listener for network events
114
- * const unsubscribe = addNetworkListener((event) => {
115
- * if (event.type === 'response') {
116
- * console.log(`${event.request.method} ${event.request.url}: ${event.response?.status}`);
117
- * }
118
- * });
119
- *
120
- * // Stop monitoring and cleanup
121
- * unsubscribe();
122
- * stopNetworkListener();
123
- * ```
124
- *
125
- * @performance Uses lazy singleton pattern to minimize memory footprint
126
- * @performance Includes URL filtering to ignore development traffic
127
- */
128
- /**
129
- * Hosts another Buoy package has claimed as its own traffic.
130
- *
131
- * A GLOBAL, not an import: `@buoy-gg/network` cannot depend on `agent-core`
132
- * (Ask Buoy is optional and this package is older), and `agent-core` ships
133
- * with no dependencies at all. `registerSelfEndpoint` in agent-core writes the
134
- * set; this reads it. Absent, undefined or the wrong shape all mean "nothing
135
- * to exclude", so nothing breaks in an app without Ask Buoy.
136
- *
137
- * WHY IT IS NOT ENOUGH TO FILTER LATER. The interceptor reads the response
138
- * body — `await processResponseBody(response.clone())` — BEFORE handing the
139
- * response back. On a streamed reply that resolves only at EOF, so an
140
- * intercepted model endpoint delivers its whole answer at once and Ask Buoy
141
- * cannot stream a word. Ignoring the URL is what keeps the socket the
142
- * caller's. It also keeps override rules off model traffic, which a broad
143
- * user-authored rule would otherwise rewrite.
144
- */
145
- /**
146
- * Content types whose body arrives over time rather than all at once.
147
- *
148
- * These are the ones where reading the clone before returning the response
149
- * changes the CALLER's behaviour instead of merely observing it.
150
- */
151
- const STREAMED_CONTENT_TYPES = ["text/event-stream", "application/x-ndjson", "application/stream+json"];
152
- const STREAMING_PLACEHOLDER = "[streaming\u2026]";
153
- function isStreamedContentType(contentType) {
154
- if (!contentType) return false;
155
- const lower = contentType.toLowerCase();
156
- return STREAMED_CONTENT_TYPES.some(t => lower.includes(t));
157
- }
158
- const INTERNAL_HOSTS_KEY = "__BUOY_INTERNAL_HOSTS__";
159
- function isInternalBuoyHost(url) {
160
- const hosts = globalThis[INTERNAL_HOSTS_KEY];
161
- if (!(hosts instanceof Set) || hosts.size === 0) return false;
162
- let authority;
163
- try {
164
- authority = new URL(url).host.toLowerCase();
165
- } catch {
166
- return false;
167
- }
168
- for (const host of hosts) {
169
- if (typeof host === "string" && host && authority === host.toLowerCase()) return true;
170
- }
171
- return false;
172
- }
173
- class NetworkListener {
174
- listeners = [];
175
- isListening = false;
176
-
177
- // Store original methods
178
-
179
- /**
180
- * The wrappers currently installed by THIS instance, for identity checks:
181
- * `stopListening` restores a global only when it is still literally ours —
182
- * assigning our saved original over someone who patched after us would wipe
183
- * their interceptor exactly the way this class gets wiped (see below).
184
- */
185
- patchedFetch = null;
186
- patchedXHROpen = null;
187
- patchedXHRSend = null;
188
- patchedXHRSetRequestHeader = null;
189
-
190
- /**
191
- * Bumped on every (re)install and on stop. Each wrapper closes over the
192
- * generation it was born with and goes silent (pure pass-through — no emit,
193
- * no counter bump) once a newer generation exists, so a wrapper stranded
194
- * inside another tool's chain can never double-report a request.
195
- */
196
- fetchGeneration = 0;
197
- xhrGeneration = 0;
198
- constructor() {
199
- // Store original methods
200
- this.originalFetch = globalThis.fetch;
201
- this.originalXHROpen = XMLHttpRequest.prototype.open;
202
- this.originalXHRSend = XMLHttpRequest.prototype.send;
203
- this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
204
- }
205
-
206
- /**
207
- * Check if URL should be ignored from network monitoring
208
- *
209
- * Filters out development-related URLs like Metro bundler, debugger proxy,
210
- * and symbolication requests to reduce noise in the network logs.
211
- *
212
- * @param url - The URL to check
213
- * @returns True if the URL should be ignored
214
- */
215
- shouldIgnoreUrl(url) {
216
- try {
217
- const parsed = new URL(url);
218
- if (parsed.hostname === "keygen.sh" || parsed.hostname.endsWith(".keygen.sh")) return true;
219
- // Buoy's own license endpoint (7.0.39+): the same traffic, new address.
220
- if ((parsed.hostname === "buoy.gg" || parsed.hostname === "www.buoy.gg") && parsed.pathname.startsWith("/api/license")) return true;
221
- const local = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "10.0.2.2";
222
- if (local && ["8081", "8802"].includes(parsed.port)) return true;
223
- // The dev server this bundle actually came from, whatever its host and
224
- // port (a LAN address, --port 8082…): Metro's own traffic — symbolicate,
225
- // logs, HMR, the bundle — is not the app's.
226
- const dev = devServerOrigin();
227
- if (dev && `${parsed.protocol}//${parsed.host}` === dev) return true;
228
- } catch {
229
- return false;
230
- }
231
- return isInternalBuoyHost(url);
232
- }
233
-
234
- // Emit event to all listeners
235
- emit(event) {
236
- this.listeners.forEach(listener => {
237
- try {
238
- listener(event);
239
- } catch (error) {
240
- // Error in event listener - continuing with others
241
- }
242
- });
243
- }
244
-
245
- /**
246
- * Parse URL to extract query parameters and clean URL
247
- *
248
- * @param url - The URL to parse
249
- * @returns Object containing cleaned URL and parsed query parameters
250
- *
251
- * @performance Uses manual parsing instead of URL constructor for better performance
252
- */
253
- parseUrl(url) {
254
- let params = null;
255
- const queryParamIdx = url.indexOf("?");
256
- if (queryParamIdx > -1) {
257
- params = {};
258
- url.substring(queryParamIdx + 1).split("&").forEach(pair => {
259
- const [key, value] = pair.split("=");
260
- if (key && value !== undefined) {
261
- params[key] = decodeURIComponent(value.replace(/\+/g, " "));
262
- }
263
- });
264
- }
265
- return {
266
- url: queryParamIdx > -1 ? url.substring(0, queryParamIdx) : url,
267
- params
268
- };
269
- }
270
-
271
- /**
272
- * Process response body with size limits to prevent memory issues
273
- * @param response - The Response object to process
274
- * @param maxSize - Maximum body size in bytes (default: 1MB)
275
- * @returns Object containing body, size, and truncation status
276
- */
277
- /**
278
- * Read a streamed body in the background, with a hard ceiling.
279
- *
280
- * Deliberately not `processResponseBody`: that one calls `.text()`, which
281
- * accumulates the WHOLE body before it checks the size, so a long-lived SSE
282
- * stream would grow unbounded in memory with nothing to stop it. This stops
283
- * at the cap and cancels the reader, and a runtime with no `body` (React
284
- * Native's own fetch) falls back to `.text()` — that response was never
285
- * streaming there in the first place.
286
- */
287
- async readStreamedBody(response, maxSize = 256 * 1024) {
288
- const body = response.body;
289
- if (!body || typeof body.getReader !== "function") {
290
- const text = await response.text();
291
- return text.length > maxSize ? {
292
- body: `${text.slice(0, maxSize)}\n\n... [truncated]`,
293
- size: text.length
294
- } : {
295
- body: text,
296
- size: text.length
297
- };
298
- }
299
- const reader = body.getReader();
300
- const decoder = new TextDecoder();
301
- let out = "";
302
- let size = 0;
303
- let capped = false;
304
- try {
305
- for (;;) {
306
- const {
307
- done,
308
- value
309
- } = await reader.read();
310
- if (done) break;
311
- size += value?.byteLength ?? 0;
312
- if (!capped) {
313
- out += decoder.decode(value, {
314
- stream: true
315
- });
316
- if (size >= maxSize) {
317
- out = `${out.slice(0, maxSize)}\n\n... [truncated]`;
318
- capped = true;
319
- break;
320
- }
321
- }
322
- }
323
- if (!capped) out += decoder.decode();
324
- } finally {
325
- try {
326
- void Promise.resolve(reader.cancel()).catch(() => {});
327
- } catch {
328
- /* already gone */
329
- }
330
- try {
331
- reader.releaseLock();
332
- } catch {
333
- /* already released */
334
- }
335
- }
336
- return {
337
- body: out,
338
- size
339
- };
340
- }
341
- async processResponseBody(response, maxSize = 1024 * 1024 // 1MB default
342
- ) {
343
- try {
344
- // Check Content-Length header first
345
- const contentLength = response.headers.get("content-length");
346
- if (contentLength) {
347
- const size = parseInt(contentLength, 10);
348
- if (!isNaN(size) && size > maxSize) {
349
- return {
350
- body: `[Response too large: ${this.formatBytes(size)}]`,
351
- size,
352
- truncated: true
353
- };
354
- }
355
- }
356
-
357
- // Read the response text
358
- const text = await response.text();
359
- const size = text.length;
360
-
361
- // Check if text exceeds max size
362
- if (size > maxSize) {
363
- const preview = text.substring(0, maxSize);
364
- const omitted = size - maxSize;
365
- return {
366
- body: `${preview}\n\n... [truncated, ${this.formatBytes(omitted)} omitted]`,
367
- size,
368
- truncated: true
369
- };
370
- }
371
-
372
- // Try to parse as JSON
373
- try {
374
- return {
375
- body: JSON.parse(text),
376
- size,
377
- truncated: false
378
- };
379
- } catch {
380
- // Return as text if not JSON
381
- return {
382
- body: text,
383
- size,
384
- truncated: false
385
- };
386
- }
387
- } catch (error) {
388
- return {
389
- body: "~~~ unable to read body ~~~",
390
- size: 0,
391
- truncated: false
392
- };
393
- }
394
- }
395
-
396
- /**
397
- * Format bytes into human-readable format
398
- * @param bytes - Number of bytes
399
- * @returns Formatted string (e.g., "1.5 MB")
400
- */
401
- formatBytes(bytes) {
402
- if (bytes === 0) return "0 B";
403
- const k = 1024;
404
- const sizes = ["B", "KB", "MB", "GB"];
405
- const i = Math.floor(Math.log(bytes) / Math.log(k));
406
- return `${Math.round(bytes / Math.pow(k, i) * 100) / 100} ${sizes[i]}`;
407
- }
408
-
409
- /**
410
- * Handle XMLHttpRequest response processing
411
- * This method processes the response and emits appropriate events
412
- */
413
- handleXHRResponse(xhr, requestId, cleanUrl, method, requestHeaders, requestData, params, startTime, isError = false, clientType = "xhr", override, condition) {
414
- const duration = startTime ? Date.now() - startTime : 0;
415
-
416
- // XHR's duration was ALREADY total (measured at readyState 4 / load), so
417
- // only the split is new. `_buoyTtfb` is stamped at HEADERS_RECEIVED; it is
418
- // absent for a request that failed before any headers arrived, and the
419
- // breakdown is then correctly omitted rather than faked as 0.
420
- const ttfb = xhr._buoyTtfb;
421
- const timings = ttfb === undefined ? undefined : {
422
- ttfb,
423
- download: Math.max(0, duration - ttfb)
424
- };
425
- if (isError || xhr.status === 0) {
426
- // Network error or aborted request
427
- this.emit({
428
- type: "error",
429
- timestamp: new Date(),
430
- duration,
431
- request: {
432
- id: requestId || "unknown",
433
- url: cleanUrl,
434
- method,
435
- headers: requestHeaders,
436
- data: requestData,
437
- params: params || undefined,
438
- client: clientType
439
- },
440
- error: {
441
- message: isError ? "Request failed" : "Network error or request aborted"
442
- },
443
- override,
444
- condition
445
- });
446
- return;
447
- }
448
-
449
- // Parse response
450
- let body;
451
- let responseSize = 0;
452
- try {
453
- const response = xhr.response;
454
-
455
- // Debug logging to help diagnose responseType issues
456
-
457
- // Try different ways to get response based on responseType
458
- if (xhr.responseType === "json" && response) {
459
- // Response is already parsed as JSON
460
- body = response;
461
- responseSize = JSON.stringify(response).length;
462
- } else if (xhr.responseType === "" || xhr.responseType === "text") {
463
- // Only access responseText when responseType allows it
464
- if (xhr.responseText) {
465
- responseSize = xhr.responseText.length;
466
- try {
467
- body = JSON.parse(xhr.responseText);
468
- } catch {
469
- body = xhr.responseText;
470
- }
471
- }
472
- } else if (xhr.responseType === "arraybuffer") {
473
- // For arraybuffer, try to decode as text and parse as JSON
474
- // This is common for Axios requests that return JSON
475
- if (response) {
476
- try {
477
- const text = new TextDecoder("utf-8").decode(response);
478
- responseSize = text.length;
479
- try {
480
- body = JSON.parse(text);
481
- } catch {
482
- // Not JSON, return as text
483
- body = text;
484
- }
485
- } catch (decodeError) {
486
- // Failed to decode, show placeholder
487
- body = `[arraybuffer response - ${response.byteLength || 0} bytes]`;
488
- responseSize = response.byteLength || 0;
489
- }
490
- } else {
491
- body = `[arraybuffer response - no data]`;
492
- responseSize = 0;
493
- }
494
- } else if (xhr.responseType === "blob") {
495
- // For blob responses, we can't synchronously read the content
496
- // Note: In React Native, most JSON responses shouldn't be blobs
497
- // but if they are, we show metadata
498
- if (response instanceof Blob) {
499
- body = `[blob response - ${response.size} bytes, type: ${response.type || "unknown"}]`;
500
- responseSize = response.size;
501
- } else if (response) {
502
- // Sometimes response might not be a Blob object but still have data
503
- // Try to handle it as an object
504
- try {
505
- body = typeof response === "string" ? JSON.parse(response) : response;
506
- responseSize = JSON.stringify(body).length;
507
- } catch {
508
- body = `[blob response - unable to parse]`;
509
- responseSize = 0;
510
- }
511
- } else {
512
- body = `[blob response - no data]`;
513
- responseSize = 0;
514
- }
515
- } else if (response) {
516
- // Fallback: try to handle the response regardless of responseType
517
- // This catches cases where Axios sets an unexpected responseType
518
- if (typeof response === "string") {
519
- body = response;
520
- responseSize = response.length;
521
- // Try to parse as JSON if it's a string
522
- try {
523
- body = JSON.parse(response);
524
- } catch {
525
- // Not JSON, keep as string
526
- }
527
- } else if (typeof response === "object") {
528
- // Already an object, use it directly
529
- body = response;
530
- responseSize = JSON.stringify(response).length;
531
- } else {
532
- body = String(response);
533
- responseSize = String(response).length;
534
- }
535
- }
536
- } catch (error) {
537
- // Failed to parse response
538
- body = "~~~ unable to read body ~~~";
539
- }
540
-
541
- // Parse response headers
542
- const responseHeaders = {};
543
- try {
544
- const headerString = xhr.getAllResponseHeaders();
545
- if (headerString) {
546
- headerString.split("\r\n").forEach(line => {
547
- if (line) {
548
- const colonIndex = line.indexOf(": ");
549
- if (colonIndex > 0) {
550
- const key = line.substring(0, colonIndex).toLowerCase();
551
- const value = line.substring(colonIndex + 2);
552
- responseHeaders[key] = value;
553
- }
554
- }
555
- });
556
- }
557
- } catch {
558
- // Ignore header parsing errors
559
- }
560
-
561
- // Emit response or error based on status. A 4xx/5xx is a completed HTTP
562
- // response, not a transport error — RN XHR leaves statusText empty, which
563
- // used to become `event.error = "HTTP 500: "` and MCP `ERR HTTP 500: no error`.
564
- if (xhrCompletionKind(xhr.status) === "response") {
565
- this.emit({
566
- type: "response",
567
- timestamp: new Date(),
568
- duration,
569
- timings,
570
- request: {
571
- id: requestId || "unknown",
572
- url: cleanUrl,
573
- method,
574
- headers: requestHeaders,
575
- data: requestData,
576
- params: params || undefined,
577
- client: clientType
578
- },
579
- response: {
580
- status: xhr.status,
581
- statusText: httpStatusText(xhr.status, xhr.statusText),
582
- headers: responseHeaders,
583
- body,
584
- size: responseSize
585
- },
586
- override,
587
- condition
588
- });
589
- } else {
590
- this.emit({
591
- type: "error",
592
- timestamp: new Date(),
593
- duration,
594
- request: {
595
- id: requestId || "unknown",
596
- url: cleanUrl,
597
- method,
598
- headers: requestHeaders,
599
- data: requestData,
600
- params: params || undefined,
601
- client: clientType
602
- },
603
- response: {
604
- status: xhr.status,
605
- statusText: xhr.statusText,
606
- headers: responseHeaders,
607
- body,
608
- size: responseSize
609
- },
610
- error: {
611
- message: "Network Error"
612
- },
613
- override,
614
- condition
615
- });
616
- }
617
- }
618
-
619
- /** Is our fetch interceptor still reachable through the global chain? */
620
- isFetchInterceptionLive() {
621
- return globalThis.fetch[BUOY_FETCH_INTERCEPTOR_FLAG] === true;
622
- }
623
-
624
- /** Is our XHR interceptor still reachable through the prototype chain? */
625
- isXhrInterceptionLive() {
626
- return XMLHttpRequest.prototype.send[BUOY_XHR_INTERCEPTOR_FLAG] === true;
627
- }
628
-
629
- /**
630
- * Both halves of the interceptor are reachable — requests actually flow
631
- * through capture. `isListening` alone cannot promise that (see
632
- * startListening); diagnostics should report THIS.
633
- */
634
- isInterceptionLive() {
635
- return this.isFetchInterceptionLive() && this.isXhrInterceptionLive();
636
- }
637
-
638
- /**
639
- * Start (or repair) interception by swizzling fetch and XMLHttpRequest.
640
- *
641
- * NOT guarded by a plain `isListening` flag: `globalThis.fetch` gets
642
- * re-assigned in the wild — Expo's winter runtime, React Native DevTools'
643
- * network panel, a second dev server's tooling, hot-reload churn. When that
644
- * happens the flag still says "listening" while every request bypasses
645
- * capture, and a flag-only guard makes the outage PERMANENT (observed as
646
- * "No network events" forever with `interceptorInstalled: true`). So this
647
- * verifies each interceptor is actually reachable (marker check) and
648
- * re-installs whichever half was clobbered, wrapping the CURRENT global so
649
- * whatever replaced us keeps working underneath. When both halves are live
650
- * this costs two property reads.
651
- *
652
- * @performance Uses method swizzling for minimal runtime overhead
653
- * @performance Includes request deduplication through ignored URL patterns
654
- */
655
- startListening() {
656
- const fetchLive = this.isListening && this.isFetchInterceptionLive();
657
- const xhrLive = this.isListening && this.isXhrInterceptionLive();
658
- if (fetchLive && xhrLive) {
659
- return;
660
- }
661
- if (!fetchLive) {
662
- this.installFetchInterceptor();
663
- }
664
- if (!xhrLive) {
665
- this.installXhrInterceptor();
666
- }
667
- this.isListening = true;
668
- if (__DEV__) {
669
- // Network listener has started monitoring fetch and XMLHttpRequest operations
670
- }
671
- }
672
-
673
- /** Swizzle `globalThis.fetch`, wrapping whatever is installed right now. */
674
- installFetchInterceptor() {
675
- const self = this;
676
- // The pass-through is the CURRENT global (not the construction-time one):
677
- // re-installing after a clobber must call through the replacement, and a
678
- // stale generation must replay the chain exactly as it stood beneath it.
679
- const passthroughFetch = globalThis.fetch.bind(globalThis);
680
- this.originalFetch = globalThis.fetch;
681
- const generation = ++this.fetchGeneration;
682
- const wrappedFetch = async (input, init) => {
683
- const url = resolveRequestUrl(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
684
-
685
- // A newer install supersedes this wrapper. If it is still stranded in
686
- // another tool's chain it must pass through silently, or one request
687
- // would be reported once per stranded generation.
688
- if (generation !== self.fetchGeneration) {
689
- return passthroughFetch(input, init);
690
- }
691
-
692
- // Skip ignored URLs
693
- if (self.shouldIgnoreUrl(url)) {
694
- return passthroughFetch(input, init);
695
- }
696
- const startTime = Date.now();
697
- const requestId = nextRequestId("fetch");
698
- const inputRequest = typeof input === "object" && "method" in input ? input : undefined;
699
- const method = init?.method ?? inputRequest?.method ?? "GET";
700
- const signal = init?.signal !== undefined ? init.signal : inputRequest?.signal;
701
- throwIfAborted(signal);
702
- const condition = snapshotNetworkCondition(url);
703
- const {
704
- url: cleanUrl,
705
- params
706
- } = self.parseUrl(url);
707
-
708
- // Parse request headers
709
- let requestHeaders = {};
710
- const headers = init?.headers ?? inputRequest?.headers;
711
- if (headers) {
712
- if (headers instanceof Headers) {
713
- headers.forEach((value, key) => {
714
- requestHeaders[key] = value;
715
- });
716
- } else if (Array.isArray(headers)) {
717
- headers.forEach(([key, value]) => {
718
- requestHeaders[key] = value;
719
- });
720
- } else {
721
- requestHeaders = {
722
- ...headers
723
- };
724
- }
725
- }
726
- Object.assign(requestHeaders, injectedHeaders(url));
727
-
728
- // Parse request body
729
- // Explicitly typed: the late emit for a streamed reply reads this from
730
- // inside a callback, which defeats the inference it used to rely on.
731
- let requestData;
732
- if (init?.body) {
733
- if (typeof init.body === "string") {
734
- try {
735
- requestData = JSON.parse(init.body);
736
- } catch {
737
- requestData = init.body;
738
- }
739
- } else {
740
- requestData = init.body;
741
- }
742
- }
743
-
744
- // Determine client type from X-Request-Client header or default to fetch
745
- const clientType = requestHeaders["X-Request-Client"] || requestHeaders["x-request-client"] || "fetch";
746
-
747
- /**
748
- * Response overrides, fetch side.
749
- *
750
- * Needed even though `send` below also resolves them: Expo installs a
751
- * NATIVE `expo/fetch` as `globalThis.fetch`, which never constructs an
752
- * XMLHttpRequest, so the XHR hook alone would never see these requests.
753
- * Skipped when `canSynthesizeResponse()` is false so a runtime without a
754
- * `Response` global degrades to a real request rather than a broken one.
755
- */
756
- const outcome = condition?.profile !== "offline" && canSynthesizeResponse() ? overrideForRequest(url, method) : null;
757
- const override = outcome ? markForOutcome(outcome) : undefined;
758
-
759
- // Emit request event
760
- self.emit({
761
- type: "request",
762
- timestamp: new Date(),
763
- request: {
764
- id: requestId || "unknown",
765
- url: cleanUrl,
766
- method,
767
- headers: requestHeaders,
768
- data: requestData,
769
- params: params || undefined,
770
- client: clientType
771
- },
772
- override,
773
- condition
774
- });
775
-
776
- // Clone before dispatch; inspect the body without delaying or consuming the app's upload.
777
- if (init?.body === undefined && inputRequest?.body && !inputRequest.bodyUsed) {
778
- try {
779
- const bodyCopy = inputRequest.clone();
780
- void self.readStreamedBody(bodyCopy).then(({
781
- body
782
- }) => {
783
- try {
784
- requestData = JSON.parse(body);
785
- } catch {
786
- requestData = body;
787
- }
788
- self.emit({
789
- type: "request-body",
790
- timestamp: new Date(),
791
- request: {
792
- id: requestId || "unknown",
793
- url: cleanUrl,
794
- method,
795
- data: requestData,
796
- client: clientType
797
- }
798
- });
799
- }).catch(() => {});
800
- } catch {
801
- // An unreadable body must not prevent the app's request.
802
- }
803
- }
804
- try {
805
- if (condition || outcome) await waitBeforeDispatch((condition?.delayMs ?? 0) + (outcome?.delayMs ?? 0), signal);
806
- throwIfAborted(signal);
807
- if (condition?.profile === "offline") throw new TypeError("Network request failed (Buoy Offline)");
808
- if (outcome?.kind === "fail") throw synthesizeFetchError(outcome);
809
- let response;
810
- if (outcome?.kind === "respond") {
811
- response = synthesizeResponse(url, outcome);
812
- } else {
813
- // The flag spans the CALL only — see `insideFetchHandoff`. It has
814
- // to be cleared before the await, or it would still be raised for
815
- // every unrelated request that starts while this one is in flight.
816
- const previousFetchHandoff = insideFetchHandoff;
817
- insideFetchHandoff = true;
818
- let pending;
819
- try {
820
- pending = passthroughFetch(input, init);
821
- } finally {
822
- insideFetchHandoff = previousFetchHandoff;
823
- }
824
- response = await pending;
825
- }
826
- // A fetch promise resolves when the HEADERS arrive, not the body — so
827
- // this is time-to-first-byte, and it is where `duration` used to stop.
828
- // That silently excluded download time, making an identical request
829
- // look faster through fetch than through XHR.
830
- const ttfb = Date.now() - startTime;
831
-
832
- /**
833
- * A STREAMED reply is never awaited here.
834
- *
835
- * `processResponseBody` reads the clone to the end, and for
836
- * `text/event-stream` the end is when the server finishes — seconds
837
- * later, for an LLM. Awaiting it made the interceptor hold the
838
- * response back until then, so every consumer of a streaming endpoint
839
- * inside an app with capture on received the whole thing at once and
840
- * could not stream a word of it. The row is emitted now with what is
841
- * known, and the body is filled in when the stream closes: the store
842
- * merges a second `response` event onto the same request id.
843
- */
844
- const streamed = typeof response.body?.getReader === "function" || isStreamedContentType(response.headers?.get?.("content-type"));
845
-
846
- // Clone response to read body with size limits
847
- const responseClone = response.clone();
848
- const {
849
- body,
850
- size: responseSize
851
- } = streamed ? {
852
- body: STREAMING_PLACEHOLDER,
853
- size: 0
854
- } : await self.processResponseBody(responseClone);
855
-
856
- // Now the body is in — this is the comparable, total number.
857
- const duration = Date.now() - startTime;
858
-
859
- /**
860
- * The split is only real on platforms whose fetch resolves at HEADERS.
861
- *
862
- * React Native's does NOT: `fetch` there is the whatwg-fetch polyfill
863
- * over XMLHttpRequest, and it resolves inside `xhr.onload` — readyState
864
- * 4, i.e. after the entire body. On device this `ttfb` is therefore the
865
- * total and the download phase collapses to ~0, so claiming a "0ms
866
- * download" would be asserting a measurement we never made.
867
- *
868
- * Omitting is the right answer rather than a problem: RN's fetch runs
869
- * through XMLHttpRequest, so the XHR interceptor below sees readyState 2
870
- * and supplies a genuine TTFB for those requests anyway. Where fetch is
871
- * native (web, Node), the gap is real and we keep it.
872
- *
873
- * Verified both ways against a server that flushes headers then holds
874
- * the body 250ms: native fetch → 16ms / 251ms; resolve-at-end → the
875
- * download phase vanishes, which is what this guard catches.
876
- */
877
- const download = Math.max(0, duration - ttfb);
878
- const timings = download > 0 ? {
879
- ttfb,
880
- download
881
- } : undefined;
882
-
883
- // Parse response headers
884
- const responseHeaders = {};
885
- response.headers.forEach((value, key) => {
886
- responseHeaders[key.toLowerCase()] = value;
887
- });
888
-
889
- // Emit response event
890
- self.emit({
891
- type: "response",
892
- timestamp: new Date(),
893
- duration,
894
- timings,
895
- request: {
896
- id: requestId || "unknown",
897
- url: cleanUrl,
898
- method,
899
- headers: requestHeaders,
900
- data: requestData,
901
- params: params || undefined,
902
- client: clientType
903
- },
904
- response: {
905
- status: response.status,
906
- statusText: response.statusText,
907
- headers: responseHeaders,
908
- body,
909
- size: responseSize
910
- },
911
- override,
912
- condition
913
- });
914
- if (streamed) {
915
- // Bounded, cancelled at the cap, and never awaited by the caller.
916
- void self.readStreamedBody(responseClone).then(late => {
917
- let capturedBody = late.body;
918
- if (response.headers?.get?.("content-type")?.includes("json")) {
919
- try {
920
- capturedBody = JSON.parse(late.body);
921
- } catch {/* Partial or malformed JSON stays text. */}
922
- }
923
- self.emit({
924
- type: "response",
925
- timestamp: new Date(),
926
- duration: Date.now() - startTime,
927
- timings,
928
- request: {
929
- id: requestId || "unknown",
930
- url: cleanUrl,
931
- method,
932
- headers: requestHeaders,
933
- data: requestData,
934
- params: params || undefined,
935
- client: clientType
936
- },
937
- response: {
938
- status: response.status,
939
- statusText: response.statusText,
940
- headers: responseHeaders,
941
- body: capturedBody,
942
- size: late.size
943
- },
944
- override,
945
- condition
946
- });
947
- }).catch(() => {
948
- /* the caller's response is unaffected either way */
949
- });
950
- }
951
- return response;
952
- } catch (error) {
953
- const duration = Date.now() - startTime;
954
-
955
- // Emit error event
956
- self.emit({
957
- type: "error",
958
- timestamp: new Date(),
959
- duration,
960
- request: {
961
- id: requestId || "unknown",
962
- url: cleanUrl,
963
- method,
964
- headers: requestHeaders,
965
- data: requestData,
966
- params: params || undefined,
967
- client: clientType
968
- },
969
- error: {
970
- message: error instanceof Error ? error.message : "Network error",
971
- stack: error instanceof Error ? error.stack : undefined
972
- },
973
- override,
974
- condition
975
- });
976
- throw error;
977
- }
978
- };
979
- wrappedFetch[BUOY_FETCH_INTERCEPTOR_FLAG] = true;
980
- this.patchedFetch = wrappedFetch;
981
- globalThis.fetch = this.patchedFetch;
982
- }
983
-
984
- /** Swizzle the XHR prototype methods, wrapping whatever is installed now. */
985
- installXhrInterceptor() {
986
- const self = this;
987
- // Same contract as the fetch half: pass through to the CURRENT methods.
988
- const passthroughOpen = XMLHttpRequest.prototype.open;
989
- const passthroughSend = XMLHttpRequest.prototype.send;
990
- const passthroughSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
991
- this.originalXHROpen = passthroughOpen;
992
- this.originalXHRSend = passthroughSend;
993
- this.originalXHRSetRequestHeader = passthroughSetRequestHeader;
994
- const generation = ++this.xhrGeneration;
995
- const pendingCapture = new WeakMap();
996
- const patchedOpen = function (method, url, async, user, password) {
997
- // Native validation runs before replacing metadata or capture listeners.
998
- const result = passthroughOpen.call(this, method, url, async ?? true, user, password);
999
- if (generation === self.xhrGeneration) {
1000
- pendingCapture.get(this)?.();
1001
- const xhr = this;
1002
- xhr._buoyRequestId = nextRequestId("xhr");
1003
- xhr._method = method;
1004
- xhr._url = resolveRequestUrl(url);
1005
- xhr._buoyStartTime = Date.now();
1006
- xhr._requestHeaders = {};
1007
- }
1008
- return result;
1009
- };
1010
-
1011
- // Track request headers
1012
- const patchedSetRequestHeader = function (header, value) {
1013
- const xhr = this;
1014
- if (generation === self.xhrGeneration && xhr._requestHeaders) {
1015
- xhr._requestHeaders[header] = value;
1016
- }
1017
- return passthroughSetRequestHeader.call(this, header, value);
1018
- };
1019
- const patchedSend = function (
1020
- // @ts-ignore - this does exist on native
1021
- data) {
1022
- // Superseded generation: silent pass-through (see the fetch wrapper).
1023
- if (generation !== self.xhrGeneration || insideFetchHandoff) {
1024
- return passthroughSend.call(this, data);
1025
- }
1026
- if (hasPendingStandardRequest(this)) throw new DOMException("The request has already been sent.", "InvalidStateError");
1027
- if (this.readyState !== 1 || this._sent) {
1028
- return passthroughSend.call(this, data);
1029
- }
1030
- const xhr = this;
1031
- const requestId = xhr._buoyRequestId;
1032
- const method = xhr._method || "GET";
1033
- const url = xhr._url || "";
1034
- const startTime = Date.now();
1035
- xhr._buoyStartTime = startTime;
1036
- xhr._buoyTtfb = undefined;
1037
- const requestHeaders = xhr._requestHeaders || {};
1038
- Object.assign(requestHeaders, injectedHeaders(url));
1039
-
1040
- // Skip ignored URLs
1041
- if (self.shouldIgnoreUrl(url)) {
1042
- return passthroughSend.call(this, data);
1043
- }
1044
- const {
1045
- url: cleanUrl,
1046
- params
1047
- } = self.parseUrl(url);
1048
-
1049
- // Parse request data
1050
- let requestData;
1051
- if (data) {
1052
- if (typeof data === "string") {
1053
- try {
1054
- requestData = JSON.parse(data);
1055
- } catch {
1056
- requestData = data;
1057
- }
1058
- } else {
1059
- requestData = data;
1060
- }
1061
- }
1062
-
1063
- // Determine client type. Honor an explicit X-Request-Client header first.
1064
- // Otherwise sniff axios via its default Accept header and fall back to
1065
- // "xhr" — defaulting to "axios" misclassified every raw XMLHttpRequest.
1066
- const explicitClient = requestHeaders["X-Request-Client"] || requestHeaders["x-request-client"];
1067
- const acceptHeader = requestHeaders["Accept"] || requestHeaders["accept"] || "";
1068
- const looksLikeAxios = acceptHeader.includes("application/json") && acceptHeader.includes("text/plain") && acceptHeader.includes("*/*");
1069
- const clientType = explicitClient || (looksLikeAxios ? "axios" : "xhr");
1070
-
1071
- /**
1072
- * Response overrides.
1073
- *
1074
- * This is the layer that covers `axios` and raw XMLHttpRequest. It also
1075
- * covers `fetch` on BARE React Native, where the whatwg-fetch polyfill
1076
- * runs through XHR — which is why the fetch wrapper raises
1077
- * `insideFetchHandoff` around its pass-through, so one request can't
1078
- * be overridden twice.
1079
- *
1080
- * Matching uses the raw `url` (query string included), which is what the
1081
- * developer sees in the list and writes into a rule. No-ops in production
1082
- * builds; see `overrideForRequest`.
1083
- */
1084
- const condition = snapshotNetworkCondition(url);
1085
- const outcome = condition?.profile === "offline" ? null : overrideForRequest(url, method);
1086
- const override = outcome ? markForOutcome(outcome) : undefined;
1087
-
1088
- // Emit request event
1089
- self.emit({
1090
- type: "request",
1091
- timestamp: new Date(),
1092
- request: {
1093
- id: requestId || "unknown",
1094
- url: cleanUrl,
1095
- method,
1096
- headers: requestHeaders,
1097
- data: requestData,
1098
- params: params || undefined,
1099
- client: clientType
1100
- },
1101
- override,
1102
- condition
1103
- });
1104
-
1105
- // Track if we've already processed this request to avoid duplicate events
1106
- let processed = false;
1107
- const processResponse = (isError = false) => {
1108
- if (processed) return;
1109
- processed = true;
1110
- self.handleXHRResponse(this, requestId || "unknown", cleanUrl, method, requestHeaders, requestData, params, startTime || 0, isError, clientType, override, condition);
1111
- // Clean up event listeners after processing to prevent memory leaks
1112
- cleanup();
1113
- };
1114
-
1115
- // Cleanup function to remove event listeners
1116
- const cleanup = () => {
1117
- pendingCapture.delete(this);
1118
- this.removeEventListener("load", loadListener);
1119
- this.removeEventListener("error", errorListener);
1120
- this.removeEventListener("abort", abortListener);
1121
- this.removeEventListener("readystatechange", readyStateListener);
1122
- };
1123
- pendingCapture.set(this, cleanup);
1124
-
1125
- // Use addEventListener to listen to events WITHOUT replacing user handlers
1126
- // This is critical because React Native's XMLHttpRequest uses EventTarget
1127
- // with getters/setters that shouldn't be overridden
1128
-
1129
- const loadListener = () => {
1130
- processResponse(false);
1131
- };
1132
- const errorListener = () => {
1133
- processResponse(true);
1134
- };
1135
- const abortListener = () => {
1136
- processResponse(true);
1137
- };
1138
- const readyStateListener = () => {
1139
- // HEADERS_RECEIVED — the XHR equivalent of a fetch promise resolving.
1140
- // This listener is already attached for the readyState-4 fallback, and
1141
- // readystatechange fires whether or not we look at it, so capturing
1142
- // TTFB here costs one comparison and one clock read.
1143
- if (this.readyState === 2) {
1144
- const xhr = this;
1145
- if (xhr._buoyTtfb === undefined && xhr._buoyStartTime) {
1146
- xhr._buoyTtfb = Date.now() - xhr._buoyStartTime;
1147
- }
1148
- }
1149
- if (this.readyState === 4 && !processed) {
1150
- processResponse(false);
1151
- }
1152
- };
1153
-
1154
- // Add event listeners that will fire alongside user handlers
1155
- this.addEventListener("load", loadListener);
1156
- this.addEventListener("error", errorListener);
1157
- this.addEventListener("abort", abortListener);
1158
- this.addEventListener("readystatechange", readyStateListener);
1159
- if (condition) {
1160
- runConditionXhr(this, url, condition, outcome, () => passthroughSend.call(this, data));
1161
- return;
1162
- }
1163
- if (outcome) {
1164
- // Listeners are attached above FIRST: `runOverride` drives the same
1165
- // readyState transitions a real response would, and the interceptor has
1166
- // to see them (readyState 2 is where TTFB is stamped).
1167
- const handled = runOverride(this, url, outcome, () => passthroughSend.call(this, data));
1168
- // `handled` is false only where this isn't React Native's own
1169
- // XMLHttpRequest and can't be driven by hand. Fall through and send for
1170
- // real rather than dropping the request on the floor.
1171
- if (handled) return;
1172
- }
1173
- return passthroughSend.call(this, data);
1174
- };
1175
- patchedSend[BUOY_XHR_INTERCEPTOR_FLAG] = true;
1176
- this.patchedXHROpen = patchedOpen;
1177
- this.patchedXHRSend = patchedSend;
1178
- this.patchedXHRSetRequestHeader = patchedSetRequestHeader;
1179
- XMLHttpRequest.prototype.open = this.patchedXHROpen;
1180
- XMLHttpRequest.prototype.send = this.patchedXHRSend;
1181
- XMLHttpRequest.prototype.setRequestHeader = this.patchedXHRSetRequestHeader;
1182
- }
1183
-
1184
- /**
1185
- * Stop listening and restore original networking methods
1186
- *
1187
- * This method restores the original fetch and XMLHttpRequest implementations,
1188
- * effectively disabling network monitoring.
1189
- */
1190
- stopListening() {
1191
- if (!this.isListening) {
1192
- return;
1193
- }
1194
-
1195
- // Silence any of our wrappers stranded inside another tool's chain — they
1196
- // check the generation on every call and now pass through without emitting.
1197
- this.fetchGeneration++;
1198
- this.xhrGeneration++;
1199
-
1200
- // Restore ONLY what is still literally ours. If another tool patched after
1201
- // us (impersonate, a debugger), assigning our saved originals would wipe
1202
- // ITS interceptor — the exact injury startListening defends against.
1203
- if (this.patchedFetch && globalThis.fetch === this.patchedFetch) {
1204
- globalThis.fetch = this.originalFetch;
1205
- }
1206
- if (this.patchedXHROpen && XMLHttpRequest.prototype.open === this.patchedXHROpen) {
1207
- XMLHttpRequest.prototype.open = this.originalXHROpen;
1208
- }
1209
- if (this.patchedXHRSend && XMLHttpRequest.prototype.send === this.patchedXHRSend) {
1210
- XMLHttpRequest.prototype.send = this.originalXHRSend;
1211
- }
1212
- if (this.patchedXHRSetRequestHeader && XMLHttpRequest.prototype.setRequestHeader === this.patchedXHRSetRequestHeader) {
1213
- XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1214
- }
1215
- this.patchedFetch = null;
1216
- this.patchedXHROpen = null;
1217
- this.patchedXHRSend = null;
1218
- this.patchedXHRSetRequestHeader = null;
1219
- this.isListening = false;
1220
- if (__DEV__) {
1221
- // Network listener has stopped monitoring and restored original methods
1222
- }
1223
- }
1224
-
1225
- /**
1226
- * Add a listener for network events
1227
- *
1228
- * @param listener - Callback function to handle network events
1229
- * @returns Unsubscribe function to remove the listener
1230
- */
1231
- addListener(listener) {
1232
- this.listeners.push(listener);
1233
-
1234
- // Return unsubscribe function
1235
- return () => {
1236
- const index = this.listeners.indexOf(listener);
1237
- if (index > -1) {
1238
- this.listeners.splice(index, 1);
1239
- }
1240
- };
1241
- }
1242
-
1243
- // Remove all listeners
1244
- removeAllListeners() {
1245
- this.listeners = [];
1246
- }
1247
-
1248
- // Check if currently listening
1249
- get isActive() {
1250
- return this.isListening;
1251
- }
1252
-
1253
- // Get number of active listeners
1254
- get listenerCount() {
1255
- return this.listeners.length;
1256
- }
1257
- }
1258
-
1259
- /**
1260
- * Lazy singleton instance holder for NetworkListener
1261
- *
1262
- * This pattern ensures only one NetworkListener instance exists throughout
1263
- * the application lifecycle while deferring instantiation until first use.
1264
- */
1265
- let _networkListener = null;
1266
-
1267
- /**
1268
- * Get or create the singleton NetworkListener instance
1269
- *
1270
- * @returns The singleton NetworkListener instance
1271
- */
1272
- const getNetworkListener = () => {
1273
- if (!_networkListener) {
1274
- _networkListener = new NetworkListener();
1275
- }
1276
- return _networkListener;
1277
- };
1278
-
1279
- /**
1280
- * Access function for the singleton NetworkListener instance
1281
- *
1282
- * @returns Function that returns the NetworkListener instance
1283
- */
1284
- export const networkListener = getNetworkListener;
1285
-
1286
- /**
1287
- * Start network traffic monitoring
1288
- *
1289
- * @example
1290
- * ```typescript
1291
- * startNetworkListener();
1292
- * console.log('Network monitoring started');
1293
- * ```
1294
- */
1295
- export const startNetworkListener = () => getNetworkListener().startListening();
1296
-
1297
- /**
1298
- * Stop network traffic monitoring
1299
- */
1300
- export const stopNetworkListener = () => _networkListener?.stopListening();
1301
-
1302
- /**
1303
- * Add a listener for network events
1304
- *
1305
- * @param listener - Callback function to handle network events
1306
- * @returns Unsubscribe function to remove the listener
1307
- *
1308
- * @example
1309
- * ```typescript
1310
- * const unsubscribe = addNetworkListener((event) => {
1311
- * console.log(`Network ${event.type}:`, event.request.url);
1312
- * });
1313
- *
1314
- * // Later...
1315
- * unsubscribe();
1316
- * ```
1317
- */
1318
- export const addNetworkListener = listener => getNetworkListener().addListener(listener);
1319
-
1320
- /**
1321
- * Remove all registered network event listeners
1322
- */
1323
- export const removeAllNetworkListeners = () => _networkListener?.removeAllListeners();
1324
-
1325
- /**
1326
- * Check if network monitoring is currently active
1327
- *
1328
- * @returns True if currently intercepting network traffic
1329
- */
1330
- export const isNetworkListening = () => _networkListener?.isActive ?? false;
1331
-
1332
- /**
1333
- * Get the number of registered network event listeners
1334
- *
1335
- * @returns Number of active listeners
1336
- */
1337
- export const getNetworkListenerCount = () => _networkListener?.listenerCount ?? 0;
1338
-
1339
- /**
1340
- * Whether both interceptors are actually reachable through the live global
1341
- * chain — the honest version of `isNetworkListening` (whose flag stays true
1342
- * even after another tool re-assigns `globalThis.fetch` over the wrapper).
1343
- */
1344
- export const isNetworkInterceptionLive = () => _networkListener?.isInterceptionLive() ?? false;
7
+ ... [truncated, ${this.formatBytes(u)} omitted]`,size:a,truncated:true}}try{return{body:JSON.parse(c),size:a,truncated:false}}catch{return{body:c,size:a,truncated:false}}}catch(s){return{body:"~~~ unable to read body ~~~",size:0,truncated:false}}}formatBytes(e){if(e===0)return"0 B";const t=1024;const s=["B","KB","MB","GB"];const c=Math.floor(Math.log(e)/Math.log(t));return`${Math.round(e/Math.pow(t,c)*100)/100} ${s[c]}`}handleXHRResponse(e,t,s,c,a,r,u,g,f=false,i="xhr",d,L){const m=g?_()-g:0;const l=e._buoyTtfb;const q=l===void 0?void 0:{ttfb:l,download:Math.max(0,m-l)};if(f||e.status===0){this.emit({type:"error",timestamp:new Date,duration:m,request:{id:t||"unknown",url:s,method:c,headers:a,data:r,params:u||void 0,client:i},error:{message:f?"Request failed":"Network error or request aborted"},override:d,condition:L});return}let n;let p=0;try{const o=e.response;if(e.responseType==="json"&&o){n=o;p=JSON.stringify(o).length}else if(e.responseType===""||e.responseType==="text"){if(e.responseText){p=e.responseText.length;try{n=JSON.parse(e.responseText)}catch{n=e.responseText}}}else if(e.responseType==="arraybuffer"){if(o){try{const y=new TextDecoder("utf-8").decode(o);p=y.length;try{n=JSON.parse(y)}catch{n=y}}catch(y){n=`[arraybuffer response - ${o.byteLength||0} bytes]`;p=o.byteLength||0}}else{n=`[arraybuffer response - no data]`;p=0}}else if(e.responseType==="blob"){if(o instanceof Blob){n=`[blob response - ${o.size} bytes, type: ${o.type||"unknown"}]`;p=o.size}else if(o){try{n=typeof o==="string"?JSON.parse(o):o;p=JSON.stringify(n).length}catch{n=`[blob response - unable to parse]`;p=0}}else{n=`[blob response - no data]`;p=0}}else if(o){if(typeof o==="string"){n=o;p=o.length;try{n=JSON.parse(o)}catch{}}else if(typeof o==="object"){n=o;p=JSON.stringify(o).length}else{n=String(o);p=String(o).length}}}catch(o){n="~~~ unable to read body ~~~"}const R={};try{const o=e.getAllResponseHeaders();if(o){o.split("\r\n").forEach(y=>{if(y){const w=y.indexOf(": ");if(w>0){const h=y.substring(0,w).toLowerCase();const H=y.substring(w+2);R[h]=H}}})}}catch{}if(se(e.status)==="response"){this.emit({type:"response",timestamp:new Date,duration:m,timings:q,request:{id:t||"unknown",url:s,method:c,headers:a,data:r,params:u||void 0,client:i},response:{status:e.status,statusText:te(e.status,e.statusText),headers:R,body:n,size:p},override:d,condition:L})}else{this.emit({type:"error",timestamp:new Date,duration:m,request:{id:t||"unknown",url:s,method:c,headers:a,data:r,params:u||void 0,client:i},response:{status:e.status,statusText:e.statusText,headers:R,body:n,size:p},error:{message:"Network Error"},override:d,condition:L})}}isFetchInterceptionLive(){return globalThis.fetch[BUOY_FETCH_INTERCEPTOR_FLAG]===true}isXhrInterceptionLive(){return XMLHttpRequest.prototype.send[BUOY_XHR_INTERCEPTOR_FLAG]===true}isInterceptionLive(){return this.isFetchInterceptionLive()&&this.isXhrInterceptionLive()}startListening(){const e=this.isListening&&this.isFetchInterceptionLive();const t=this.isListening&&this.isXhrInterceptionLive();if(e&&t){return}if(!e){this.installFetchInterceptor()}if(!t){this.installXhrInterceptor()}this.isListening=true;if(__DEV__){}}installFetchInterceptor(){const e=this;const t=globalThis.fetch.bind(globalThis);this.originalFetch=globalThis.fetch;const s=++this.fetchGeneration;const c=async(a,r)=>{const u=U(typeof a==="string"?a:a instanceof URL?a.href:a.url);if(s!==e.fetchGeneration){return t(a,r)}if(e.shouldIgnoreUrl(u)){return t(a,r)}const g=_();const f=B("fetch");const i=typeof a==="object"&&"method"in a?a:void 0;const d=r?.method??i?.method??"GET";const L=r?.signal!==void 0?r.signal:i?.signal;G(L);const m=z(u);const{url:l,params:q}=e.parseUrl(u);let n={};const p=r?.headers??i?.headers;if(p){if(p instanceof Headers){p.forEach((h,H)=>{n[H]=h})}else if(Array.isArray(p)){p.forEach(([h,H])=>{n[h]=H})}else{n={...p}}}Object.assign(n,$(u));let R;if(r?.body){if(typeof r.body==="string"){try{R=JSON.parse(r.body)}catch{R=r.body}}else{R=r.body}}const o=n["X-Request-Client"]||n["x-request-client"]||"fetch";const y=m?.profile!=="offline"&&Q()?j(u,d,n):null;const w=y?J(y):void 0;e.emit({type:"request",timestamp:new Date,request:{id:f||"unknown",url:l,method:d,headers:n,data:R,params:q||void 0,client:o},override:w,condition:m});if(r?.body===void 0&&i?.body&&!i.bodyUsed){try{const h=i.clone();void e.readStreamedBody(h).then(({body:H})=>{try{R=JSON.parse(H)}catch{R=H}e.emit({type:"request-body",timestamp:new Date,request:{id:f||"unknown",url:l,method:d,data:R,client:o}})}).catch(()=>{})}catch{}}try{if(m||y)await K((m?.delayMs??0)+(y?.delayMs??0),L);G(L);if(m?.profile==="offline")throw new TypeError("Network request failed (Buoy Offline)");if(y?.kind==="fail")throw Z(y);let h;if(y?.kind==="respond"){h=ee(u,y)}else{const T=D;D=true;let X;try{X=t(a,r)}finally{D=T}h=await X}const H=_()-g;const S=typeof h.body?.getReader==="function"||ie(h.headers?.get?.("content-type"));const E=h.clone();const{body:F,size:I}=S?{body:P,size:0}:await e.processResponseBody(E);const x=_()-g;const C=Math.max(0,x-H);const M=C>0?{ttfb:H,download:C}:void 0;const N={};h.headers.forEach((T,X)=>{N[X.toLowerCase()]=T});e.emit({type:"response",timestamp:new Date,duration:x,timings:M,request:{id:f||"unknown",url:l,method:d,headers:n,data:R,params:q||void 0,client:o},response:{status:h.status,statusText:h.statusText,headers:N,body:F,size:I},override:w,condition:m});if(S){void e.readStreamedBody(E).then(T=>{let X=T.body;if(h.headers?.get?.("content-type")?.includes("json")){try{X=JSON.parse(T.body)}catch{}}e.emit({type:"response",timestamp:new Date,duration:_()-g,timings:M,request:{id:f||"unknown",url:l,method:d,headers:n,data:R,params:q||void 0,client:o},response:{status:h.status,statusText:h.statusText,headers:N,body:X,size:T.size},override:w,condition:m})}).catch(()=>{})}return h}catch(h){const H=_()-g;e.emit({type:"error",timestamp:new Date,duration:H,request:{id:f||"unknown",url:l,method:d,headers:n,data:R,params:q||void 0,client:o},error:{message:h instanceof Error?h.message:"Network error",stack:h instanceof Error?h.stack:void 0},override:w,condition:m});throw h}};c[BUOY_FETCH_INTERCEPTOR_FLAG]=true;this.patchedFetch=c;globalThis.fetch=this.patchedFetch}installXhrInterceptor(){const e=this;const t=XMLHttpRequest.prototype.open;const s=XMLHttpRequest.prototype.send;const c=XMLHttpRequest.prototype.setRequestHeader;this.originalXHROpen=t;this.originalXHRSend=s;this.originalXHRSetRequestHeader=c;const a=++this.xhrGeneration;const r=new WeakMap;const u=function(i,d,L,m,l){const q=t.call(this,i,d,L??true,m,l);if(a===e.xhrGeneration){r.get(this)?.();const n=this;n._buoyRequestId=B("xhr");n._method=i;n._url=U(d);n._buoyStartTime=_();n._requestHeaders={}}return q};const g=function(i,d){const L=this;if(a===e.xhrGeneration&&L._requestHeaders){L._requestHeaders[i]=d}return c.call(this,i,d)};const f=function(i){if(a!==e.xhrGeneration||D){return s.call(this,i)}if(W(this))throw new DOMException("The request has already been sent.","InvalidStateError");if(this.readyState!==1||this._sent){return s.call(this,i)}const d=this;const L=d._buoyRequestId;const m=d._method||"GET";const l=d._url||"";const q=_();d._buoyStartTime=q;d._buoyTtfb=void 0;const n=d._requestHeaders||{};Object.assign(n,$(l));if(e.shouldIgnoreUrl(l)){return s.call(this,i)}const{url:p,params:R}=e.parseUrl(l);let o;if(i){if(typeof i==="string"){try{o=JSON.parse(i)}catch{o=i}}else{o=i}}const y=n["X-Request-Client"]||n["x-request-client"];const w=n["Accept"]||n["accept"]||"";const h=w.includes("application/json")&&w.includes("text/plain")&&w.includes("*/*");const H=y||(h?"axios":"xhr");const S=z(l);const E=S?.profile==="offline"?null:j(l,m,n);const F=E?J(E):void 0;e.emit({type:"request",timestamp:new Date,request:{id:L||"unknown",url:p,method:m,headers:n,data:o,params:R||void 0,client:H},override:F,condition:S});let I=false;const x=(v=false)=>{if(I)return;I=true;e.handleXHRResponse(this,L||"unknown",p,m,n,o,R,q||0,v,H,F,S);C()};const C=()=>{r.delete(this);this.removeEventListener("load",M);this.removeEventListener("error",N);this.removeEventListener("abort",T);this.removeEventListener("readystatechange",X)};r.set(this,C);const M=()=>{x(false)};const N=()=>{x(true)};const T=()=>{x(true)};const X=()=>{if(this.readyState===2){const v=this;if(v._buoyTtfb===void 0&&v._buoyStartTime){v._buoyTtfb=_()-v._buoyStartTime}}if(this.readyState===4&&!I){x(false)}};this.addEventListener("load",M);this.addEventListener("error",N);this.addEventListener("abort",T);this.addEventListener("readystatechange",X);if(S){Y(this,l,S,E,()=>s.call(this,i));return}if(E){const v=V(this,l,E,()=>s.call(this,i));if(v)return}return s.call(this,i)};f[BUOY_XHR_INTERCEPTOR_FLAG]=true;this.patchedXHROpen=u;this.patchedXHRSend=f;this.patchedXHRSetRequestHeader=g;XMLHttpRequest.prototype.open=this.patchedXHROpen;XMLHttpRequest.prototype.send=this.patchedXHRSend;XMLHttpRequest.prototype.setRequestHeader=this.patchedXHRSetRequestHeader}stopListening(){if(!this.isListening){return}this.fetchGeneration++;this.xhrGeneration++;if(this.patchedFetch&&globalThis.fetch===this.patchedFetch){globalThis.fetch=this.originalFetch}if(this.patchedXHROpen&&XMLHttpRequest.prototype.open===this.patchedXHROpen){XMLHttpRequest.prototype.open=this.originalXHROpen}if(this.patchedXHRSend&&XMLHttpRequest.prototype.send===this.patchedXHRSend){XMLHttpRequest.prototype.send=this.originalXHRSend}if(this.patchedXHRSetRequestHeader&&XMLHttpRequest.prototype.setRequestHeader===this.patchedXHRSetRequestHeader){XMLHttpRequest.prototype.setRequestHeader=this.originalXHRSetRequestHeader}this.patchedFetch=null;this.patchedXHROpen=null;this.patchedXHRSend=null;this.patchedXHRSetRequestHeader=null;this.isListening=false;if(__DEV__){}}adoptEarlyRequests(){const e=globalThis.__BUOY_EARLY_NETWORK__;if(e?.version!==1)return;for(const t of e.adopt()){try{if(t.kind==="fetch")this.adoptEarlyFetch(t);else this.adoptEarlyXhr(t)}catch{}}}adoptEarlyFetch(e){const{input:t,init:s}=e;const c=typeof t==="object"&&"method"in t?t:void 0;const a=U(typeof t==="string"?t:t instanceof URL?t.href:t.url);if(this.shouldIgnoreUrl(a))return;const r=B("fetch");const u=s?.method??c?.method??"GET";const{url:g,params:f}=this.parseUrl(a);const i=re(s?.headers??c?.headers);let d=s?.body;if(typeof d==="string"){try{d=JSON.parse(d)}catch{}}const L=i["X-Request-Client"]||i["x-request-client"]||"fetch";const m=()=>({id:r,url:g,method:u,headers:i,data:d,params:f||void 0,client:L});this.emit({type:"request",timestamp:new Date(e.start),request:m()});if(e.requestCopy){void this.readStreamedBody(e.requestCopy).then(({body:l})=>{try{d=JSON.parse(l)}catch{d=l}this.emit({type:"request-body",timestamp:new Date,request:{id:r,url:g,method:u,data:d,client:L}})}).catch(()=>{})}void e.settled.then(async({response:l,clone:q,error:n,headersAt:p})=>{if(!l){this.emit({type:"error",timestamp:new Date,duration:p-e.start,request:m(),error:{message:n instanceof Error?n.message:"Network error",stack:n instanceof Error?n.stack:void 0}});return}const{body:R,size:o}=q?await this.processResponseBody(q):{body:P,size:0};const y=Math.max(0,_()-e.start);const w=p-e.start;const h={};l.headers.forEach((H,S)=>{h[S.toLowerCase()]=H});this.emit({type:"response",timestamp:new Date,duration:y,timings:y>w?{ttfb:w,download:y-w}:void 0,request:m(),response:{status:l.status,statusText:l.statusText,headers:h,body:R,size:o}})})}adoptEarlyXhr(e){const t=U(e.url);if(this.shouldIgnoreUrl(t))return;const s=B("xhr");const c=e.method.toUpperCase();const{url:a,params:r}=this.parseUrl(t);let u=e.body;if(typeof u==="string"){try{u=JSON.parse(u)}catch{}}const g=e.headers["X-Request-Client"]||e.headers["x-request-client"]||"xhr";this.emit({type:"request",timestamp:new Date(e.start),request:{id:s,url:a,method:c,headers:e.headers,data:u,params:r||void 0,client:g}});const f=e.xhr;const i=()=>{const d=(f._buoyEarlyEnd??_())-e.start;this.handleXHRResponse(f,s,a,c,e.headers,u,r,_()-d,false,g)};if(f.readyState===4)i();else f.addEventListener("loadend",i,{once:true})}addListener(e){this.listeners.push(e);return()=>{const t=this.listeners.indexOf(e);if(t>-1){this.listeners.splice(t,1)}}}removeAllListeners(){this.listeners=[]}get isActive(){return this.isListening}get listenerCount(){return this.listeners.length}}let k=null;const A=()=>{if(!k){k=new de}return k};export const networkListener=A;export const startNetworkListener=()=>A().startListening();export const stopNetworkListener=()=>k?.stopListening();export const addNetworkListener=b=>A().addListener(b);export const removeAllNetworkListeners=()=>k?.removeAllListeners();export const isNetworkListening=()=>k?.isActive??false;export const getNetworkListenerCount=()=>k?.listenerCount??0;export const isNetworkInterceptionLive=()=>k?.isInterceptionLive()??false;