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