@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.
- package/README.md +11 -79
- package/lib/commonjs/index.js +1 -325
- package/lib/commonjs/index.web.js +1 -420
- package/lib/commonjs/network/components/DataViewer.web.js +1 -879
- package/lib/commonjs/network/components/NetworkCopyScreen.web.js +1 -48
- package/lib/commonjs/network/components/NetworkCopySettingsView.js +27 -755
- package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +14 -1242
- package/lib/commonjs/network/components/NetworkDetailScreen.web.js +1 -101
- package/lib/commonjs/network/components/NetworkDetailStepper.js +1 -108
- package/lib/commonjs/network/components/NetworkDetailStepper.web.js +1 -298
- package/lib/commonjs/network/components/NetworkEventDetailView.js +15 -1130
- package/lib/commonjs/network/components/NetworkEventDetailView.web.js +15 -1484
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +2 -401
- package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +2 -820
- package/lib/commonjs/network/components/NetworkFilterScreen.web.js +1 -55
- package/lib/commonjs/network/components/NetworkFilterViewV3.js +1 -442
- package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1 -1310
- package/lib/commonjs/network/components/NetworkHeaderLive.js +1 -375
- package/lib/commonjs/network/components/NetworkHeaderLive.web.js +1 -623
- package/lib/commonjs/network/components/NetworkHeaderMenu.js +1 -190
- package/lib/commonjs/network/components/NetworkListScreen.js +1 -404
- package/lib/commonjs/network/components/NetworkListScreen.web.js +1 -625
- package/lib/commonjs/network/components/NetworkModal.js +1 -786
- package/lib/commonjs/network/components/NetworkNumberPrompt.js +1 -178
- package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +1 -232
- package/lib/commonjs/network/components/NetworkOverrideEditor.js +1 -814
- package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +1 -942
- package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +1 -107
- package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +1 -133
- package/lib/commonjs/network/components/NetworkOverridesButton.js +1 -90
- package/lib/commonjs/network/components/NetworkOverridesButton.web.js +1 -143
- package/lib/commonjs/network/components/NetworkOverridesScreen.js +1 -543
- package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +1 -830
- package/lib/commonjs/network/components/NetworkSavedScreen.js +1 -226
- package/lib/commonjs/network/components/NetworkSavedScreen.web.js +1 -463
- package/lib/commonjs/network/components/NetworkUpgradeBanner.js +1 -76
- package/lib/commonjs/network/components/response-editor/Explorer.js +1 -973
- package/lib/commonjs/network/components/response-editor/Explorer.web.js +1 -1136
- package/lib/commonjs/network/components/response-editor/deleteNestedDataByPath.js +1 -56
- package/lib/commonjs/network/components/response-editor/editorContext.js +1 -33
- package/lib/commonjs/network/components/response-editor/updateNestedDataByPath.js +1 -61
- package/lib/commonjs/network/conditions/NetworkConditionsControl.js +1 -235
- package/lib/commonjs/network/conditions/NetworkConditionsControl.test.js +1 -136
- package/lib/commonjs/network/conditions/NetworkConditionsIcon.js +1 -33
- package/lib/commonjs/network/conditions/NetworkFloatingTool.js +1 -310
- package/lib/commonjs/network/conditions/NetworkFloatingTool.test.js +1 -219
- package/lib/commonjs/network/conditions/store.js +1 -75
- package/lib/commonjs/network/conditions/wait.js +1 -43
- package/lib/commonjs/network/conditions/xhr.js +1 -110
- package/lib/commonjs/network/hooks/useLiveNetworkEvent.js +1 -62
- package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +1 -83
- package/lib/commonjs/network/hooks/useNetworkEventList.js +1 -108
- package/lib/commonjs/network/hooks/useNetworkEvents.js +1 -318
- package/lib/commonjs/network/hooks/useNetworkHasMatches.js +1 -56
- package/lib/commonjs/network/hooks/useNetworkSaved.js +1 -38
- package/lib/commonjs/network/hooks/useNetworkSelection.js +1 -53
- package/lib/commonjs/network/hooks/useOverrideRules.js +1 -36
- package/lib/commonjs/network/hooks/useTickEveryMinute.js +1 -21
- package/lib/commonjs/network/overrides/applyToFetch.js +1 -80
- package/lib/commonjs/network/overrides/applyToXhr.js +2 -453
- package/lib/commonjs/network/overrides/engine.js +1 -93
- package/lib/commonjs/network/overrides/matchRule.js +1 -81
- package/lib/commonjs/network/overrides/overriddenRows.js +1 -0
- package/lib/commonjs/network/overrides/overrideRulesStore.js +1 -550
- package/lib/commonjs/network/overrides/presets.js +1 -70
- package/lib/commonjs/network/overrides/resolveOverride.js +1 -158
- package/lib/commonjs/network/overrides/types.js +1 -90
- package/lib/commonjs/network/sync/networkBodyResolver.js +1 -39
- package/lib/commonjs/network/sync/networkSyncAdapter.js +1 -882
- package/lib/commonjs/network/sync/overrideHonesty.release.test.js +1 -36
- package/lib/commonjs/network/types/index.js +1 -5
- package/lib/commonjs/network/utils/extractOperationName.js +1 -80
- package/lib/commonjs/network/utils/filterNetworkEvents.js +1 -96
- package/lib/commonjs/network/utils/formatGraphQLVariables.js +1 -219
- package/lib/commonjs/network/utils/formatting.js +1 -30
- package/lib/commonjs/network/utils/generateCurl.js +2 -71
- package/lib/commonjs/network/utils/generateNetworkCopyText.js +37 -166
- package/lib/commonjs/network/utils/httpCompletion.js +1 -37
- package/lib/commonjs/network/utils/networkEventStore.js +1 -393
- package/lib/commonjs/network/utils/networkListener.js +4 -1259
- package/lib/commonjs/network/utils/networkSavedStore.js +3 -577
- package/lib/commonjs/network/utils/networkSelectionStore.js +1 -193
- package/lib/commonjs/network/utils/realNow.js +1 -0
- package/lib/commonjs/network/utils/requestId.js +1 -0
- package/lib/commonjs/network/utils/resolveRequestUrl.js +1 -0
- package/lib/commonjs/network/utils/selectSavedEvents.js +1 -39
- package/lib/commonjs/network/utils/startNetworkCapture.js +1 -0
- package/lib/commonjs/network/utils/withPinnedFirst.js +1 -32
- package/lib/commonjs/preset.js +1 -102
- package/lib/module/index.js +1 -103
- package/lib/module/index.web.js +1 -82
- package/lib/module/network/components/DataViewer.web.js +1 -875
- package/lib/module/network/components/NetworkCopyScreen.web.js +1 -44
- package/lib/module/network/components/NetworkCopySettingsView.js +27 -750
- package/lib/module/network/components/NetworkCopySettingsView.web.js +14 -1237
- package/lib/module/network/components/NetworkDetailScreen.web.js +1 -97
- package/lib/module/network/components/NetworkDetailStepper.js +1 -104
- package/lib/module/network/components/NetworkDetailStepper.web.js +1 -296
- package/lib/module/network/components/NetworkEventDetailView.js +15 -1129
- package/lib/module/network/components/NetworkEventDetailView.web.js +15 -1482
- package/lib/module/network/components/NetworkEventItemCompact.js +2 -398
- package/lib/module/network/components/NetworkEventItemCompact.web.js +2 -818
- package/lib/module/network/components/NetworkFilterScreen.web.js +1 -51
- package/lib/module/network/components/NetworkFilterViewV3.js +1 -438
- package/lib/module/network/components/NetworkFilterViewV3.web.js +1 -1306
- package/lib/module/network/components/NetworkHeaderLive.js +1 -366
- package/lib/module/network/components/NetworkHeaderLive.web.js +1 -617
- package/lib/module/network/components/NetworkHeaderMenu.js +1 -184
- package/lib/module/network/components/NetworkListScreen.js +1 -400
- package/lib/module/network/components/NetworkListScreen.web.js +1 -623
- package/lib/module/network/components/NetworkModal.js +1 -781
- package/lib/module/network/components/NetworkNumberPrompt.js +1 -174
- package/lib/module/network/components/NetworkNumberPrompt.web.js +1 -230
- package/lib/module/network/components/NetworkOverrideEditor.js +1 -811
- package/lib/module/network/components/NetworkOverrideEditor.web.js +1 -939
- package/lib/module/network/components/NetworkOverrideHeaderButton.js +1 -104
- package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +1 -131
- package/lib/module/network/components/NetworkOverridesButton.js +1 -87
- package/lib/module/network/components/NetworkOverridesButton.web.js +1 -141
- package/lib/module/network/components/NetworkOverridesScreen.js +1 -539
- package/lib/module/network/components/NetworkOverridesScreen.web.js +1 -826
- package/lib/module/network/components/NetworkSavedScreen.js +1 -222
- package/lib/module/network/components/NetworkSavedScreen.web.js +1 -461
- package/lib/module/network/components/NetworkUpgradeBanner.js +1 -72
- package/lib/module/network/components/response-editor/Explorer.js +1 -972
- package/lib/module/network/components/response-editor/Explorer.web.js +1 -1133
- package/lib/module/network/components/response-editor/deleteNestedDataByPath.js +1 -51
- package/lib/module/network/components/response-editor/editorContext.js +1 -29
- package/lib/module/network/components/response-editor/updateNestedDataByPath.js +1 -56
- package/lib/module/network/conditions/NetworkConditionsControl.js +1 -231
- package/lib/module/network/conditions/NetworkConditionsControl.test.js +1 -135
- package/lib/module/network/conditions/NetworkConditionsIcon.js +1 -29
- package/lib/module/network/conditions/NetworkFloatingTool.js +1 -304
- package/lib/module/network/conditions/NetworkFloatingTool.test.js +1 -218
- package/lib/module/network/conditions/store.js +1 -65
- package/lib/module/network/conditions/wait.js +1 -38
- package/lib/module/network/conditions/xhr.js +1 -106
- package/lib/module/network/hooks/useLiveNetworkEvent.js +1 -58
- package/lib/module/network/hooks/useNetworkBadgeCounts.js +1 -79
- package/lib/module/network/hooks/useNetworkEventList.js +1 -104
- package/lib/module/network/hooks/useNetworkEvents.js +1 -314
- package/lib/module/network/hooks/useNetworkHasMatches.js +1 -52
- package/lib/module/network/hooks/useNetworkSaved.js +1 -34
- package/lib/module/network/hooks/useNetworkSelection.js +1 -46
- package/lib/module/network/hooks/useOverrideRules.js +1 -32
- package/lib/module/network/hooks/useTickEveryMinute.js +1 -11
- package/lib/module/network/overrides/applyToFetch.js +1 -72
- package/lib/module/network/overrides/applyToXhr.js +2 -446
- package/lib/module/network/overrides/engine.js +1 -85
- package/lib/module/network/overrides/matchRule.js +1 -75
- package/lib/module/network/overrides/overriddenRows.js +1 -0
- package/lib/module/network/overrides/overrideRulesStore.js +1 -547
- package/lib/module/network/overrides/presets.js +1 -65
- package/lib/module/network/overrides/resolveOverride.js +1 -152
- package/lib/module/network/overrides/types.js +1 -86
- package/lib/module/network/sync/networkBodyResolver.js +1 -34
- package/lib/module/network/sync/networkSyncAdapter.js +1 -880
- package/lib/module/network/sync/overrideHonesty.release.test.js +1 -35
- package/lib/module/network/types/index.js +1 -3
- package/lib/module/network/utils/extractOperationName.js +1 -76
- package/lib/module/network/utils/filterNetworkEvents.js +1 -92
- package/lib/module/network/utils/formatGraphQLVariables.js +1 -213
- package/lib/module/network/utils/formatting.js +1 -9
- package/lib/module/network/utils/generateCurl.js +2 -67
- package/lib/module/network/utils/generateNetworkCopyText.js +37 -162
- package/lib/module/network/utils/httpCompletion.js +1 -32
- package/lib/module/network/utils/networkEventStore.js +1 -391
- package/lib/module/network/utils/networkListener.js +4 -1249
- package/lib/module/network/utils/networkSavedStore.js +3 -572
- package/lib/module/network/utils/networkSelectionStore.js +1 -188
- package/lib/module/network/utils/realNow.js +1 -0
- package/lib/module/network/utils/requestId.js +1 -0
- package/lib/module/network/utils/resolveRequestUrl.js +1 -0
- package/lib/module/network/utils/selectSavedEvents.js +1 -35
- package/lib/module/network/utils/startNetworkCapture.js +1 -0
- package/lib/module/network/utils/withPinnedFirst.js +1 -28
- package/lib/module/preset.js +1 -100
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/network/overrides/applyToXhr.d.ts +1 -0
- package/lib/typescript/network/overrides/engine.d.ts +1 -1
- package/lib/typescript/network/overrides/matchRule.d.ts +1 -1
- package/lib/typescript/network/overrides/overriddenRows.d.ts +16 -0
- package/lib/typescript/network/overrides/resolveOverride.d.ts +2 -2
- package/lib/typescript/network/overrides/types.d.ts +9 -0
- package/lib/typescript/network/utils/networkListener.d.ts +14 -4
- package/lib/typescript/network/utils/networkSavedStore.d.ts +4 -4
- package/lib/typescript/network/utils/realNow.d.ts +18 -0
- package/lib/typescript/network/utils/requestId.d.ts +2 -0
- package/lib/typescript/network/utils/resolveRequestUrl.d.ts +3 -0
- package/lib/typescript/network/utils/startNetworkCapture.d.ts +6 -0
- package/lib/web/index.mjs +145 -0
- package/package.json +32 -10
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/index.web.d.ts.map +0 -1
- package/lib/typescript/network/components/DataViewer.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkModal.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkNumberPrompt.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverrideEditor.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverridesButton.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkSavedScreen.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +0 -1
- package/lib/typescript/network/components/NetworkUpgradeBanner.d.ts.map +0 -1
- package/lib/typescript/network/components/response-editor/Explorer.d.ts.map +0 -1
- package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +0 -1
- package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts.map +0 -1
- package/lib/typescript/network/components/response-editor/editorContext.d.ts.map +0 -1
- package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts.map +0 -1
- package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts.map +0 -1
- package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts.map +0 -1
- package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts.map +0 -1
- package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts.map +0 -1
- package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts.map +0 -1
- package/lib/typescript/network/conditions/store.d.ts.map +0 -1
- package/lib/typescript/network/conditions/wait.d.ts.map +0 -1
- package/lib/typescript/network/conditions/xhr.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useLiveNetworkEvent.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useNetworkSaved.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useNetworkSelection.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useOverrideRules.d.ts.map +0 -1
- package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +0 -1
- package/lib/typescript/network/overrides/applyToFetch.d.ts.map +0 -1
- package/lib/typescript/network/overrides/applyToXhr.d.ts.map +0 -1
- package/lib/typescript/network/overrides/engine.d.ts.map +0 -1
- package/lib/typescript/network/overrides/matchRule.d.ts.map +0 -1
- package/lib/typescript/network/overrides/overrideRulesStore.d.ts.map +0 -1
- package/lib/typescript/network/overrides/presets.d.ts.map +0 -1
- package/lib/typescript/network/overrides/resolveOverride.d.ts.map +0 -1
- package/lib/typescript/network/overrides/types.d.ts.map +0 -1
- package/lib/typescript/network/sync/networkBodyResolver.d.ts.map +0 -1
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/network/sync/overrideHonesty.release.test.d.ts.map +0 -1
- package/lib/typescript/network/types/index.d.ts.map +0 -1
- package/lib/typescript/network/utils/extractOperationName.d.ts.map +0 -1
- package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +0 -1
- package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +0 -1
- package/lib/typescript/network/utils/formatting.d.ts.map +0 -1
- package/lib/typescript/network/utils/generateCurl.d.ts.map +0 -1
- package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +0 -1
- package/lib/typescript/network/utils/httpCompletion.d.ts.map +0 -1
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +0 -1
- package/lib/typescript/network/utils/networkListener.d.ts.map +0 -1
- package/lib/typescript/network/utils/networkSavedStore.d.ts.map +0 -1
- package/lib/typescript/network/utils/networkSelectionStore.d.ts.map +0 -1
- package/lib/typescript/network/utils/selectSavedEvents.d.ts.map +0 -1
- package/lib/typescript/network/utils/withPinnedFirst.d.ts.map +0 -1
- 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
|
-
|
|
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;
|