@buoy-gg/network 7.0.40 → 7.0.42

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