@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,882 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.networkSyncAdapter = exports.SNAPSHOT_HEADER_VALUE_LIMIT = exports.SNAPSHOT_EVENTS_BUDGET = exports.SNAPSHOT_BODY_INLINE_LIMIT = void 0;
|
|
7
|
-
var _store = require("../conditions/store");
|
|
8
|
-
var _networkEventStore = require("../utils/networkEventStore");
|
|
9
|
-
var _networkSavedStore = require("../utils/networkSavedStore");
|
|
10
|
-
var _overrideRulesStore = require("../overrides/overrideRulesStore");
|
|
11
|
-
var _presets = require("../overrides/presets");
|
|
12
|
-
var _engine = require("../overrides/engine");
|
|
13
|
-
var _networkListener = require("../utils/networkListener");
|
|
14
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
15
|
-
/**
|
|
16
|
-
* Request/response bodies larger than this are NOT streamed in the list
|
|
17
|
-
* snapshot. Prefer the cached size proxy already on the event (free); if that
|
|
18
|
-
* field is missing or wrong (0 for a fat/cyclic body), fall back to an
|
|
19
|
-
* O(limit) walk. Stripped bodies come back via `getEventBody` on detail open.
|
|
20
|
-
*/
|
|
21
|
-
const SNAPSHOT_BODY_INLINE_LIMIT = exports.SNAPSHOT_BODY_INLINE_LIMIT = 16 * 1024;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Total inline budget for one snapshot's event list, held under the emit
|
|
25
|
-
* layer's 2MB so a snapshot is never DROPPED (a drop leaves the whole panel
|
|
26
|
-
* stale). Per-field caps alone don't get there: 500 requests with 9KB
|
|
27
|
-
* responses are all individually legal and still add up to ~4.7MB. Newest
|
|
28
|
-
* events keep their bodies inline (that's what a developer is looking at);
|
|
29
|
-
* once the budget is spent, older events degrade to metadata + fetch-on-demand.
|
|
30
|
-
*/
|
|
31
|
-
const SNAPSHOT_EVENTS_BUDGET = exports.SNAPSHOT_EVENTS_BUDGET = 1.25 * 1024 * 1024;
|
|
32
|
-
function shouldStripBody(data, reportedSize) {
|
|
33
|
-
if (data === undefined) return false;
|
|
34
|
-
// The cached size proxy already on the event is free when it's right; the
|
|
35
|
-
// walk is the fallback for when it's missing or wrong (0 for a fat body).
|
|
36
|
-
if ((reportedSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT) return true;
|
|
37
|
-
return (0, _sharedUi.isOverWireBudget)(data, SNAPSHOT_BODY_INLINE_LIMIT);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** One event's wire form plus its measured cost, so the budget pass is free. */
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Per-event wire cache. `networkEventStore` never mutates an event in place —
|
|
44
|
-
* a response/override update replaces the object — so keying on identity is
|
|
45
|
-
* correct and self-invalidating, and a completed request is converted once
|
|
46
|
-
* instead of on all ~5 snapshots per second for as long as it stays in the
|
|
47
|
-
* list. This is what keeps the guards off the hot path.
|
|
48
|
-
*/
|
|
49
|
-
const wireCache = new WeakMap();
|
|
50
|
-
const strippedCache = new WeakMap();
|
|
51
|
-
|
|
52
|
-
/** Per-field guards: oversized bodies out, heavy headers/variables slimmed. */
|
|
53
|
-
function toWireEvent(event) {
|
|
54
|
-
const cached = wireCache.get(event);
|
|
55
|
-
if (cached) return cached;
|
|
56
|
-
let entry;
|
|
57
|
-
try {
|
|
58
|
-
entry = buildWireEvent(event);
|
|
59
|
-
} catch {
|
|
60
|
-
// A body with a throwing getter or `toJSON` can blow up the size walk
|
|
61
|
-
// itself. Degrading this ONE event to metadata beats letting getSnapshot
|
|
62
|
-
// throw, which would take the whole panel down with it.
|
|
63
|
-
entry = buildMetadataOnly(event);
|
|
64
|
-
}
|
|
65
|
-
wireCache.set(event, entry);
|
|
66
|
-
return entry;
|
|
67
|
-
}
|
|
68
|
-
function buildMetadataOnly(event) {
|
|
69
|
-
const stripped = {
|
|
70
|
-
...event,
|
|
71
|
-
requestData: undefined,
|
|
72
|
-
responseData: undefined,
|
|
73
|
-
graphqlVariables: undefined,
|
|
74
|
-
requestBodyOmitted: event.requestData !== undefined ? true : undefined,
|
|
75
|
-
responseBodyOmitted: event.responseData !== undefined ? true : undefined
|
|
76
|
-
};
|
|
77
|
-
// The payload fields are gone, so measuring this is safe even when measuring
|
|
78
|
-
// the original blew up. Fall back to a nominal cost only if even THIS throws
|
|
79
|
-
// (a hostile getter on the event itself rather than on its body).
|
|
80
|
-
let bytes = 1024;
|
|
81
|
-
try {
|
|
82
|
-
bytes = (0, _sharedUi.approxJsonSize)(stripped, SNAPSHOT_EVENTS_BUDGET).bytes;
|
|
83
|
-
} catch {
|
|
84
|
-
/* keep the nominal estimate */
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
event: stripped,
|
|
88
|
-
bytes
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function buildWireEvent(event) {
|
|
92
|
-
const stripRequest = shouldStripBody(event.requestData, event.requestSize);
|
|
93
|
-
const stripResponse = shouldStripBody(event.responseData, event.responseSize);
|
|
94
|
-
const graphqlVariables = toWireGraphqlVariables(event.graphqlVariables);
|
|
95
|
-
const requestHeaders = toWireHeaders(event.requestHeaders);
|
|
96
|
-
const responseHeaders = toWireHeaders(event.responseHeaders);
|
|
97
|
-
const changed = stripRequest || stripResponse || graphqlVariables !== event.graphqlVariables || requestHeaders !== event.requestHeaders || responseHeaders !== event.responseHeaders;
|
|
98
|
-
let wire = event;
|
|
99
|
-
if (changed) {
|
|
100
|
-
// `undefined` keys are omitted by JSON serialization, so dropping the data
|
|
101
|
-
// shrinks the wire payload. Sizes stay HONEST — the explicit
|
|
102
|
-
// request/responseBodyOmitted flags are what tell the dashboard to fetch,
|
|
103
|
-
// matching `OverrideRule.bodyOmitted`. Full values come from `getEventBody`.
|
|
104
|
-
wire = {
|
|
105
|
-
...event
|
|
106
|
-
};
|
|
107
|
-
if (stripRequest) {
|
|
108
|
-
wire.requestData = undefined;
|
|
109
|
-
wire.requestBodyOmitted = true;
|
|
110
|
-
}
|
|
111
|
-
if (stripResponse) {
|
|
112
|
-
wire.responseData = undefined;
|
|
113
|
-
wire.responseBodyOmitted = true;
|
|
114
|
-
}
|
|
115
|
-
if (graphqlVariables !== event.graphqlVariables) {
|
|
116
|
-
wire.graphqlVariables = graphqlVariables;
|
|
117
|
-
}
|
|
118
|
-
if (requestHeaders !== event.requestHeaders) {
|
|
119
|
-
wire.requestHeaders = requestHeaders;
|
|
120
|
-
wire.headersOmitted = true;
|
|
121
|
-
}
|
|
122
|
-
if (responseHeaders !== event.responseHeaders) {
|
|
123
|
-
wire.responseHeaders = responseHeaders;
|
|
124
|
-
wire.headersOmitted = true;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
event: wire,
|
|
129
|
-
bytes: (0, _sharedUi.approxJsonSize)(wire, SNAPSHOT_EVENTS_BUDGET).bytes
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Metadata-only form, used for the tail of the list once the snapshot budget
|
|
135
|
-
* is spent. Method/url/status/timing all stay — only the payloads go.
|
|
136
|
-
*/
|
|
137
|
-
function toStrippedEvent(event) {
|
|
138
|
-
const cached = strippedCache.get(event);
|
|
139
|
-
if (cached) return cached;
|
|
140
|
-
const {
|
|
141
|
-
event: wire
|
|
142
|
-
} = toWireEvent(event);
|
|
143
|
-
const stripped = {
|
|
144
|
-
...wire
|
|
145
|
-
};
|
|
146
|
-
if (stripped.requestData !== undefined) {
|
|
147
|
-
stripped.requestData = undefined;
|
|
148
|
-
stripped.requestBodyOmitted = true;
|
|
149
|
-
}
|
|
150
|
-
if (stripped.responseData !== undefined) {
|
|
151
|
-
stripped.responseData = undefined;
|
|
152
|
-
stripped.responseBodyOmitted = true;
|
|
153
|
-
}
|
|
154
|
-
if (stripped.graphqlVariables !== undefined) {
|
|
155
|
-
stripped.graphqlVariables = toWireGraphqlVariables(stripped.graphqlVariables, /* force */true);
|
|
156
|
-
}
|
|
157
|
-
const entry = {
|
|
158
|
-
event: stripped,
|
|
159
|
-
bytes: (0, _sharedUi.approxJsonSize)(stripped, SNAPSHOT_EVENTS_BUDGET).bytes
|
|
160
|
-
};
|
|
161
|
-
strippedCache.set(event, entry);
|
|
162
|
-
return entry;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Spend the snapshot budget newest-first. The store keeps events newest-first
|
|
167
|
-
* already, so this hands the developer full detail on what they are actually
|
|
168
|
-
* looking at and degrades the tail rather than dropping the whole panel.
|
|
169
|
-
*/
|
|
170
|
-
function fitEventsToBudget(events) {
|
|
171
|
-
const out = new Array(events.length);
|
|
172
|
-
let spent = 0;
|
|
173
|
-
for (let i = 0; i < events.length; i++) {
|
|
174
|
-
const full = toWireEvent(events[i]);
|
|
175
|
-
if (spent + full.bytes <= SNAPSHOT_EVENTS_BUDGET) {
|
|
176
|
-
out[i] = full.event;
|
|
177
|
-
spent += full.bytes;
|
|
178
|
-
} else {
|
|
179
|
-
const lean = toStrippedEvent(events[i]);
|
|
180
|
-
out[i] = lean.event;
|
|
181
|
-
spent += lean.bytes;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* GraphQL variables are copied off `request.data.variables` onto their own
|
|
189
|
-
* field for the list subtitle (`GetPokemon › Sandshrew`). Capping requestData
|
|
190
|
-
* still left that copy on the wire. Keep short primitives for the subtitle;
|
|
191
|
-
* drop nested/fat values (file uploads, base64). Full variables stay in
|
|
192
|
-
* `requestData` on-device and come back via `getEventBody`.
|
|
193
|
-
*/
|
|
194
|
-
function toWireGraphqlVariables(vars, force = false) {
|
|
195
|
-
if (!vars) return vars;
|
|
196
|
-
if (!force && !shouldStripBody(vars, undefined)) return vars;
|
|
197
|
-
const slim = {};
|
|
198
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
199
|
-
if (typeof value === "string" && value.length <= 64) slim[key] = value;else if (typeof value === "number" || typeof value === "boolean") {
|
|
200
|
-
slim[key] = value;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return Object.keys(slim).length > 0 ? slim : undefined;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Header values (Cookie, Authorization, custom JSON) still rode every
|
|
208
|
-
* snapshot after bodies were stripped. 500 events × a 4KB session cookie
|
|
209
|
-
* is a 2MB drop of the whole Network panel. Keep short values inline
|
|
210
|
-
* (content-type, accept); truncate the rest. Full maps stay on-device
|
|
211
|
-
* and come back via `getEventBody`.
|
|
212
|
-
*
|
|
213
|
-
* Returns the SAME object when nothing was truncated: callers use identity to
|
|
214
|
-
* decide whether anything was omitted, so a fresh `{}` here would flag every
|
|
215
|
-
* header-less event as truncated (and defeat the no-copy fast path).
|
|
216
|
-
*/
|
|
217
|
-
const SNAPSHOT_HEADER_VALUE_LIMIT = exports.SNAPSHOT_HEADER_VALUE_LIMIT = 64;
|
|
218
|
-
function toWireHeaders(headers) {
|
|
219
|
-
if (!headers) return headers;
|
|
220
|
-
let changed = false;
|
|
221
|
-
const slim = {};
|
|
222
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
223
|
-
if (typeof value !== "string") continue;
|
|
224
|
-
if (value.length <= SNAPSHOT_HEADER_VALUE_LIMIT) {
|
|
225
|
-
slim[key] = value;
|
|
226
|
-
} else {
|
|
227
|
-
slim[key] = `${value.slice(0, SNAPSHOT_HEADER_VALUE_LIMIT)}… [${value.length - SNAPSHOT_HEADER_VALUE_LIMIT} more]`;
|
|
228
|
-
changed = true;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return changed ? slim : headers;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Drop oversized rule bodies from the snapshot, flagging what was dropped.
|
|
236
|
-
*
|
|
237
|
-
* Same limit and same reasoning as `stripLargeBodies` above — and the same
|
|
238
|
-
* hazard, only worse: an override body is authored once and then rides EVERY
|
|
239
|
-
* snapshot for as long as the rule exists, whereas an event ages out of the
|
|
240
|
-
* list. `draftFromEvent` seeds a rule with the real response, so 500KB is an
|
|
241
|
-
* ordinary size here, not a pathological one.
|
|
242
|
-
*/
|
|
243
|
-
const stripLargeRuleBodies = state => {
|
|
244
|
-
let changed = false;
|
|
245
|
-
const rules = state.rules.map(rule => {
|
|
246
|
-
if (!rule.body || rule.body.length <= SNAPSHOT_BODY_INLINE_LIMIT) {
|
|
247
|
-
return rule;
|
|
248
|
-
}
|
|
249
|
-
changed = true;
|
|
250
|
-
return {
|
|
251
|
-
...rule,
|
|
252
|
-
body: undefined,
|
|
253
|
-
bodyOmitted: true
|
|
254
|
-
};
|
|
255
|
-
});
|
|
256
|
-
// Identity is preserved when nothing was stripped: the desktop mirror feeds
|
|
257
|
-
// this straight into a `useEffect` dependency, and a fresh object every
|
|
258
|
-
// snapshot would re-run it several times a second.
|
|
259
|
-
return changed ? {
|
|
260
|
-
...state,
|
|
261
|
-
rules
|
|
262
|
-
} : state;
|
|
263
|
-
};
|
|
264
|
-
const resolveEvent = id => _networkEventStore.networkEventStore.getEventById(id) ?? _networkSavedStore.networkSavedStore.getEventById(id);
|
|
265
|
-
const readId = params => params && typeof params === "object" ? params.id : undefined;
|
|
266
|
-
const readBool = (params, key) => !!(params && typeof params === "object" && params[key]);
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Stamp an override write receipt with whether the rule can actually fire.
|
|
270
|
-
*
|
|
271
|
-
* Omitted entirely when overrides work, so a dev build's receipts are
|
|
272
|
-
* unchanged and no existing consumer sees a new field it has to reason about.
|
|
273
|
-
* Present only when the answer is "saved, but inert" — which is exactly when a
|
|
274
|
-
* caller relaying success to a person would otherwise be wrong.
|
|
275
|
-
*/
|
|
276
|
-
const inactiveStamp = () => (0, _engine.overridesCanApply)() ? {} : {
|
|
277
|
-
active: false,
|
|
278
|
-
inactiveReason: _engine.OVERRIDES_INACTIVE_REASON
|
|
279
|
-
};
|
|
280
|
-
const VALID_KINDS = ["respond", "fail", "delay"];
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Turn an override rule sent over the wire into a draft the store will accept.
|
|
284
|
-
*
|
|
285
|
-
* Validated rather than trusted: these params arrive from the dashboard and
|
|
286
|
-
* from MCP clients, and a malformed rule doesn't just fail — it gets PERSISTED
|
|
287
|
-
* and then re-applied to real traffic on every launch. `urlPattern` is the one
|
|
288
|
-
* field with no sane default, so its absence rejects the whole call.
|
|
289
|
-
*/
|
|
290
|
-
function readRuleDraft(params,
|
|
291
|
-
/**
|
|
292
|
-
* Prefill from a captured request, as the UI's Override button does. Any
|
|
293
|
-
* field the caller sent explicitly still wins over it.
|
|
294
|
-
*/
|
|
295
|
-
base) {
|
|
296
|
-
if (!params || typeof params !== "object") return null;
|
|
297
|
-
const raw = params.rule ?? params;
|
|
298
|
-
if (!raw || typeof raw !== "object") return null;
|
|
299
|
-
const rule = raw;
|
|
300
|
-
const urlPattern = typeof rule.urlPattern === "string" && rule.urlPattern.trim() ? rule.urlPattern.trim() : base?.urlPattern ?? "";
|
|
301
|
-
if (!urlPattern) return null;
|
|
302
|
-
const kind = VALID_KINDS.includes(rule.kind) ? rule.kind : base?.kind ?? "respond";
|
|
303
|
-
return {
|
|
304
|
-
id: typeof rule.id === "string" ? rule.id : undefined,
|
|
305
|
-
enabled: rule.enabled !== false,
|
|
306
|
-
name: typeof rule.name === "string" ? rule.name : base?.name,
|
|
307
|
-
urlPattern,
|
|
308
|
-
methods: Array.isArray(rule.methods) ? rule.methods.filter(m => typeof m === "string").map(m => m.toUpperCase()) : base?.methods,
|
|
309
|
-
kind,
|
|
310
|
-
status: typeof rule.status === "number" ? rule.status : base?.status,
|
|
311
|
-
statusText: typeof rule.statusText === "string" ? rule.statusText : base?.statusText,
|
|
312
|
-
headers: rule.headers && typeof rule.headers === "object" ? rule.headers : base?.headers,
|
|
313
|
-
// Objects are accepted and serialized so a caller can send JSON directly
|
|
314
|
-
// instead of pre-stringifying it, which is the mistake everyone makes once.
|
|
315
|
-
body: patchedBody(rule, base),
|
|
316
|
-
failKind: rule.failKind === "timeout" ? "timeout" : undefined,
|
|
317
|
-
delayMs: typeof rule.delayMs === "number" ? rule.delayMs : undefined,
|
|
318
|
-
times: typeof rule.times === "number" ? rule.times : null,
|
|
319
|
-
alternate: rule.alternate === true
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
const isPlainObject = v => !!v && typeof v === "object" && !Array.isArray(v);
|
|
323
|
-
/**
|
|
324
|
-
* Objects merge key by key; arrays and primitives are replaced. Collects two
|
|
325
|
-
* dangers instead of writing them: `shrunk` (a non-empty array shorter than
|
|
326
|
-
* the one it replaces) and `breaks` (an array/object field overwritten by null
|
|
327
|
-
* or a primitive — the shape the app renders, changed out from under it). The
|
|
328
|
-
* handler refuses either unless forced.
|
|
329
|
-
*
|
|
330
|
-
* This stays local while the typed-edit RULE is shared: it is the merge itself,
|
|
331
|
-
* not the check, and it reports network-specific dangers the store surfaces do
|
|
332
|
-
* not have. `shapeKind` comes from the shared module so both agree on what
|
|
333
|
-
* counts as an array.
|
|
334
|
-
*/
|
|
335
|
-
function deepMerge(base, patch, path = "", issues = {
|
|
336
|
-
shrunk: [],
|
|
337
|
-
breaks: []
|
|
338
|
-
}) {
|
|
339
|
-
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
340
|
-
if (patch.length > 0 && patch.length < base.length) {
|
|
341
|
-
issues.shrunk.push({
|
|
342
|
-
path: path || "(root)",
|
|
343
|
-
have: base.length,
|
|
344
|
-
sent: patch.length
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
return patch;
|
|
348
|
-
}
|
|
349
|
-
if (!isPlainObject(base) || !isPlainObject(patch)) return patch;
|
|
350
|
-
const out = {
|
|
351
|
-
...base
|
|
352
|
-
};
|
|
353
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
354
|
-
const p = path ? `${path}.${k}` : k;
|
|
355
|
-
if (k in base) {
|
|
356
|
-
const from = (0, _sharedUi.shapeKind)(base[k]);
|
|
357
|
-
const to = (0, _sharedUi.shapeKind)(v);
|
|
358
|
-
if ((from === "array" || from === "object") && from !== to) {
|
|
359
|
-
issues.breaks.push({
|
|
360
|
-
path: p,
|
|
361
|
-
from,
|
|
362
|
-
to
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
out[k] = deepMerge(base[k], v, p, issues);
|
|
366
|
-
} else {
|
|
367
|
-
out[k] = v;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return out;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/** Typed-edit violations a bodyPatch would cause against the seed body, or []. */
|
|
374
|
-
function patchViolations(rule, seed) {
|
|
375
|
-
if (rule.body != null || !isPlainObject(rule.bodyPatch) || typeof seed !== "string") return [];
|
|
376
|
-
try {
|
|
377
|
-
return (0, _sharedUi.typedEditViolations)(JSON.parse(seed), rule.bodyPatch, {
|
|
378
|
-
noun: "response body"
|
|
379
|
-
});
|
|
380
|
-
} catch {
|
|
381
|
-
return [];
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* The rule's body: explicit `body` wins, else `bodyPatch` deep-merged into the
|
|
387
|
-
* seeded body (from `fromRequestId`, or the rule being replaced), else the seed.
|
|
388
|
-
*
|
|
389
|
-
* `bodyPatch` exists for the same reason `setQueryData` has `merge`: a remote
|
|
390
|
-
* caller reads the real response through a wire that truncates big payloads,
|
|
391
|
-
* edits one field, and pastes the torn object back as `body`. The app then
|
|
392
|
-
* renders a response missing half its fields. With a patch the caller sends
|
|
393
|
-
* `{ stats: [...] }` and the other 30KB stay exactly as the server sent them.
|
|
394
|
-
*/
|
|
395
|
-
function patchedBody(rule, base) {
|
|
396
|
-
if (typeof rule.body === "string") return rule.body;
|
|
397
|
-
if (rule.body != null) return JSON.stringify(rule.body, null, 2);
|
|
398
|
-
const seed = base?.body ?? (typeof rule.id === "string" ? _overrideRulesStore.overrideRulesStore.getState().rules.find(r => r.id === rule.id)?.body : undefined);
|
|
399
|
-
if (isPlainObject(rule.bodyPatch) && typeof seed === "string") {
|
|
400
|
-
try {
|
|
401
|
-
return JSON.stringify(deepMerge(JSON.parse(seed), rule.bodyPatch), null, 2);
|
|
402
|
-
} catch {
|
|
403
|
-
return seed;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return seed;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Replacing a rule by id inherits every field the caller did not send, so a
|
|
411
|
-
* remote driver can flip one thing — `{ id, bodyPatch }`, `{ id, status: 500 }`
|
|
412
|
-
* — without re-sending the urlPattern it never had to know.
|
|
413
|
-
*/
|
|
414
|
-
function existingRule(params) {
|
|
415
|
-
if (!params || typeof params !== "object") return null;
|
|
416
|
-
const raw = params.rule ?? params;
|
|
417
|
-
if (!raw || typeof raw !== "object") return null;
|
|
418
|
-
const id = raw.id;
|
|
419
|
-
if (typeof id !== "string") return null;
|
|
420
|
-
const existing = _overrideRulesStore.overrideRulesStore.getState().rules.find(r => r.id === id);
|
|
421
|
-
return existing ? {
|
|
422
|
-
...existing
|
|
423
|
-
} : null;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* The body a remote surface never received, when it says so.
|
|
428
|
-
*
|
|
429
|
-
* A dashboard editing a rule whose body was stripped from the snapshot has
|
|
430
|
-
* nothing to send back for it. Without this the save would write `body:
|
|
431
|
-
* undefined` and silently destroy the payload — the one field a rule exists for.
|
|
432
|
-
*/
|
|
433
|
-
function preservedBody(params) {
|
|
434
|
-
if (!params || typeof params !== "object") return null;
|
|
435
|
-
const raw = params.rule ?? params;
|
|
436
|
-
if (!raw || typeof raw !== "object") return null;
|
|
437
|
-
const rule = raw;
|
|
438
|
-
if (rule.bodyOmitted !== true || typeof rule.id !== "string") return null;
|
|
439
|
-
const existing = _overrideRulesStore.overrideRulesStore.getState().rules.find(candidate => candidate.id === rule.id);
|
|
440
|
-
return existing ? {
|
|
441
|
-
body: existing.body
|
|
442
|
-
} : null;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Resolve `fromRequestId` into the same draft the UI's Override button builds.
|
|
447
|
-
*
|
|
448
|
-
* Worth routing through `draftFromEvent` rather than letting a caller paste the
|
|
449
|
-
* URL: it drops the query string to `*`, which is the difference between a rule
|
|
450
|
-
* that fires and one that sits at 0 hits against a cache-busting client.
|
|
451
|
-
*/
|
|
452
|
-
function baseFromRequest(params) {
|
|
453
|
-
if (!params || typeof params !== "object") return null;
|
|
454
|
-
const id = params.fromRequestId;
|
|
455
|
-
if (typeof id !== "string" || !id) return null;
|
|
456
|
-
const event = resolveEvent(id);
|
|
457
|
-
return event ? (0, _presets.draftFromEvent)(event) : null;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Sync adapter for the network tool, consumed by @buoy-gg/external-sync's
|
|
462
|
-
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
463
|
-
* this package doesn't need a dependency on it).
|
|
464
|
-
*
|
|
465
|
-
* Subscribing starts the network interceptor, so requests are only captured
|
|
466
|
-
* while a dashboard is watching the network panel — or while an MCP
|
|
467
|
-
* client holds a capture-mode watch (`devtool-watch` with `stream:false`),
|
|
468
|
-
* which records without asking the device to push snapshots.
|
|
469
|
-
*
|
|
470
|
-
* v2: large bodies are stripped from the snapshot and fetched lazily via
|
|
471
|
-
* `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
|
|
472
|
-
*
|
|
473
|
-
* v3: the payload became an OBJECT — `{ events, saved }` — so pinned/saved
|
|
474
|
-
* requests reach the dashboard. They can't ride as flags on the events: a pin
|
|
475
|
-
* outlives the event that produced it (that is its whole purpose), so it has
|
|
476
|
-
* to be its own list. Dashboards that predate v3 read `payload` as an array;
|
|
477
|
-
* `NetworkPanel`/`NetworkInline` accept either shape.
|
|
478
|
-
*/
|
|
479
|
-
const networkSyncAdapter = exports.networkSyncAdapter = {
|
|
480
|
-
dispose: () => {
|
|
481
|
-
_overrideRulesStore.overrideRulesStore.setEnabled(false);
|
|
482
|
-
},
|
|
483
|
-
// v5: honest request/responseSize + explicit request/responseBodyOmitted
|
|
484
|
-
// flags, and a total snapshot budget so the list degrades instead of the
|
|
485
|
-
// whole panel being dropped at the emit layer.
|
|
486
|
-
version: 6,
|
|
487
|
-
getSnapshot: () => {
|
|
488
|
-
// Piggyback the interception health check on the watch loop: while a
|
|
489
|
-
// dashboard/MCP watch is open this runs continuously, so a clobbered
|
|
490
|
-
// fetch/XHR patch (see networkListener.startListening) heals within one
|
|
491
|
-
// snapshot tick instead of staying dead for the session. Two property
|
|
492
|
-
// reads when healthy.
|
|
493
|
-
_networkEventStore.networkEventStore.ensureInterception();
|
|
494
|
-
return {
|
|
495
|
-
conditions: {
|
|
496
|
-
version: 1,
|
|
497
|
-
runtimeId: _store.networkConditionsRuntimeId,
|
|
498
|
-
profile: (0, _store.getNetworkCondition)(),
|
|
499
|
-
available: (0, _store.conditionsCanApply)()
|
|
500
|
-
},
|
|
501
|
-
events: fitEventsToBudget(_networkEventStore.networkEventStore.getEvents()),
|
|
502
|
-
/**
|
|
503
|
-
* Response-override rules live on the DEVICE — that's the only place they
|
|
504
|
-
* can run, since a rule is applied inside the app's own XMLHttpRequest.
|
|
505
|
-
* Remote surfaces mirror this and forward their edits back as actions.
|
|
506
|
-
*/
|
|
507
|
-
overrides: stripLargeRuleBodies(_overrideRulesStore.overrideRulesStore.getState()),
|
|
508
|
-
// Saved snapshots go through the same body strip. They are few (capped in
|
|
509
|
-
// the store) but they ride on EVERY snapshot, so a fat one would land in
|
|
510
|
-
// the same 200ms loop that the body-stripping exists to protect.
|
|
511
|
-
saved: _networkSavedStore.networkSavedStore.getRecords().map(record => ({
|
|
512
|
-
...record,
|
|
513
|
-
event: toWireEvent(record.event).event
|
|
514
|
-
}))
|
|
515
|
-
};
|
|
516
|
-
},
|
|
517
|
-
subscribe: onChange => {
|
|
518
|
-
// Pinning with no traffic in flight still has to reach the dashboard, so
|
|
519
|
-
// both stores drive the snapshot.
|
|
520
|
-
const unsubscribeEvents = _networkEventStore.networkEventStore.subscribeToEvents(onChange);
|
|
521
|
-
const unsubscribeConditions = (0, _store.subscribeNetworkConditions)(onChange);
|
|
522
|
-
const unsubscribeSaved = _networkSavedStore.networkSavedStore.subscribe(onChange);
|
|
523
|
-
// Same reasoning for rules: editing one with no traffic in flight must
|
|
524
|
-
// still reach the dashboard. Subscribing also hydrates the rule list.
|
|
525
|
-
const unsubscribeOverrides = _overrideRulesStore.overrideRulesStore.subscribe(onChange);
|
|
526
|
-
return () => {
|
|
527
|
-
unsubscribeConditions();
|
|
528
|
-
unsubscribeEvents();
|
|
529
|
-
unsubscribeSaved();
|
|
530
|
-
unsubscribeOverrides();
|
|
531
|
-
};
|
|
532
|
-
},
|
|
533
|
-
actions: {
|
|
534
|
-
getNetworkConditions: () => ({
|
|
535
|
-
runtimeId: _store.networkConditionsRuntimeId,
|
|
536
|
-
profile: (0, _store.getNetworkCondition)(),
|
|
537
|
-
available: (0, _store.conditionsCanApply)()
|
|
538
|
-
}),
|
|
539
|
-
setNetworkConditions: params => {
|
|
540
|
-
const profile = params && typeof params === "object" ? params.profile : undefined;
|
|
541
|
-
return {
|
|
542
|
-
runtimeId: _store.networkConditionsRuntimeId,
|
|
543
|
-
profile: (0, _store.setNetworkCondition)(profile),
|
|
544
|
-
available: (0, _store.conditionsCanApply)()
|
|
545
|
-
};
|
|
546
|
-
},
|
|
547
|
-
clearEvents: () => {
|
|
548
|
-
_networkEventStore.networkEventStore.clearEvents();
|
|
549
|
-
},
|
|
550
|
-
/**
|
|
551
|
-
* Return the full (un-stripped) request/response bodies for one event,
|
|
552
|
-
* fetched on demand by the dashboard when its detail view opens. Falls
|
|
553
|
-
* back to the saved store, which may hold the only remaining copy of a
|
|
554
|
-
* request that has since been cleared. Returns null if neither has it.
|
|
555
|
-
*/
|
|
556
|
-
getEventBody: params => {
|
|
557
|
-
const id = readId(params);
|
|
558
|
-
if (!id) return null;
|
|
559
|
-
const event = resolveEvent(id);
|
|
560
|
-
if (!event) return null;
|
|
561
|
-
return {
|
|
562
|
-
// Identity + the override mark ride along so a caller (Ask Buoy's
|
|
563
|
-
// receipt, an MCP session) can say WHOSE body this is and whether an
|
|
564
|
-
// override rule produced it — a mocked response rendered as "live"
|
|
565
|
-
// is the fabrication problem wearing a chrome suit.
|
|
566
|
-
url: event.url ?? null,
|
|
567
|
-
method: event.method ?? null,
|
|
568
|
-
status: event.status ?? null,
|
|
569
|
-
override: event.override ?? null,
|
|
570
|
-
requestData: event.requestData ?? null,
|
|
571
|
-
responseData: event.responseData ?? null,
|
|
572
|
-
requestHeaders: event.requestHeaders ?? null,
|
|
573
|
-
responseHeaders: event.responseHeaders ?? null
|
|
574
|
-
};
|
|
575
|
-
},
|
|
576
|
-
/** Pin/unpin a request from the dashboard. */
|
|
577
|
-
setPinned: params => {
|
|
578
|
-
const id = readId(params);
|
|
579
|
-
if (!id) return null;
|
|
580
|
-
const event = resolveEvent(id);
|
|
581
|
-
if (!event) return null;
|
|
582
|
-
const wanted = readBool(params, "pinned");
|
|
583
|
-
// `flagsForEventId`, not `isPinned` — the id may be a `saved:<key>`
|
|
584
|
-
// snapshot, which the live-id Sets deliberately don't carry.
|
|
585
|
-
const current = (0, _networkSavedStore.flagsForEventId)(_networkSavedStore.networkSavedStore.getState(), id).pinned;
|
|
586
|
-
if (current === wanted) return {
|
|
587
|
-
ok: true
|
|
588
|
-
};
|
|
589
|
-
return _networkSavedStore.networkSavedStore.togglePin(event);
|
|
590
|
-
},
|
|
591
|
-
/** Save/unsave a request from the dashboard. */
|
|
592
|
-
setSaved: params => {
|
|
593
|
-
const id = readId(params);
|
|
594
|
-
if (!id) return null;
|
|
595
|
-
const event = resolveEvent(id);
|
|
596
|
-
if (!event) return null;
|
|
597
|
-
const wanted = readBool(params, "saved");
|
|
598
|
-
const current = (0, _networkSavedStore.flagsForEventId)(_networkSavedStore.networkSavedStore.getState(), id).saved;
|
|
599
|
-
if (current === wanted) return {
|
|
600
|
-
ok: true
|
|
601
|
-
};
|
|
602
|
-
return _networkSavedStore.networkSavedStore.toggleSave(event);
|
|
603
|
-
},
|
|
604
|
-
/** Drop one pinned/saved record by its stable key. */
|
|
605
|
-
removeSavedRecord: params => {
|
|
606
|
-
const key = params && typeof params === "object" ? params.key : undefined;
|
|
607
|
-
if (!key) return null;
|
|
608
|
-
_networkSavedStore.networkSavedStore.remove(key);
|
|
609
|
-
return {
|
|
610
|
-
ok: true
|
|
611
|
-
};
|
|
612
|
-
},
|
|
613
|
-
clearSavedRequests: () => {
|
|
614
|
-
_networkSavedStore.networkSavedStore.clearSaved();
|
|
615
|
-
return {
|
|
616
|
-
ok: true
|
|
617
|
-
};
|
|
618
|
-
},
|
|
619
|
-
clearPinnedRequests: () => {
|
|
620
|
-
_networkSavedStore.networkSavedStore.clearPinned();
|
|
621
|
-
return {
|
|
622
|
-
ok: true
|
|
623
|
-
};
|
|
624
|
-
},
|
|
625
|
-
// =========================================================================
|
|
626
|
-
// RESPONSE OVERRIDES
|
|
627
|
-
// =========================================================================
|
|
628
|
-
|
|
629
|
-
/** Master switch — turns every rule off without losing any of them. */
|
|
630
|
-
setOverridesEnabled: params => {
|
|
631
|
-
_overrideRulesStore.overrideRulesStore.setEnabled(readBool(params, "enabled"));
|
|
632
|
-
return {
|
|
633
|
-
ok: true,
|
|
634
|
-
enabled: _overrideRulesStore.overrideRulesStore.getState().enabled,
|
|
635
|
-
// `active` is the difference between "we saved it" and "it is doing
|
|
636
|
-
// something". A remote driver reads this and tells the user the truth.
|
|
637
|
-
...inactiveStamp()
|
|
638
|
-
};
|
|
639
|
-
},
|
|
640
|
-
/** Create a rule, or replace one by id. */
|
|
641
|
-
upsertOverrideRule: params => {
|
|
642
|
-
const base = baseFromRequest(params) ?? preservedBody(params) ?? existingRule(params);
|
|
643
|
-
const rawRule = params?.rule ?? params;
|
|
644
|
-
// `bodyPath` + `bodyValue` are sugar over `bodyPatch`, resolved against the
|
|
645
|
-
// real response before anything else looks at the rule — so the keyed
|
|
646
|
-
// expansion, the typed-edit guard and the merge below all run on an
|
|
647
|
-
// ordinary patch and gain no special cases. This is the same form the
|
|
648
|
-
// state tools take; it exists here because a response body is the surface
|
|
649
|
-
// where the caller is LEAST likely to have seen the shape (it read the
|
|
650
|
-
// body over a wire that truncates at 24,000 characters), and therefore
|
|
651
|
-
// most likely to get the nesting wrong.
|
|
652
|
-
if (rawRule && typeof rawRule.bodyPath === "string" && rawRule.body == null) {
|
|
653
|
-
const seed = base?.body ?? (typeof rawRule.id === "string" ? _overrideRulesStore.overrideRulesStore.getState().rules.find(r => r.id === rawRule.id)?.body : undefined);
|
|
654
|
-
if (typeof seed !== "string") {
|
|
655
|
-
return {
|
|
656
|
-
ok: false,
|
|
657
|
-
error: "bodyPath needs something to patch: send fromRequestId (the captured request whose real response it addresses) or the id of an existing rule."
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
if (rawRule.bodyValue === undefined) {
|
|
661
|
-
return {
|
|
662
|
-
ok: false,
|
|
663
|
-
error: `Refused: sent \`bodyPath\` without \`bodyValue\`. Say what to set "${rawRule.bodyPath}" to.`
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
let parsed;
|
|
667
|
-
try {
|
|
668
|
-
parsed = JSON.parse(seed);
|
|
669
|
-
} catch {
|
|
670
|
-
return {
|
|
671
|
-
ok: false,
|
|
672
|
-
error: "The captured response body is not JSON, so it cannot be addressed by path. Send a whole `body` instead."
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
const built = (0, _sharedUi.patchFromPath)(parsed, rawRule.bodyPath, rawRule.bodyValue);
|
|
676
|
-
if (!built.ok) return {
|
|
677
|
-
ok: false,
|
|
678
|
-
error: `Refused: ${built.error}`
|
|
679
|
-
};
|
|
680
|
-
rawRule.bodyPatch = built.patch;
|
|
681
|
-
delete rawRule.bodyPath;
|
|
682
|
-
delete rawRule.bodyValue;
|
|
683
|
-
}
|
|
684
|
-
if (rawRule && isPlainObject(rawRule.bodyPatch) && rawRule.body == null) {
|
|
685
|
-
const seed = base?.body ?? (typeof rawRule.id === "string" ? _overrideRulesStore.overrideRulesStore.getState().rules.find(r => r.id === rawRule.id)?.body : undefined);
|
|
686
|
-
if (typeof seed !== "string") {
|
|
687
|
-
return {
|
|
688
|
-
ok: false,
|
|
689
|
-
error: "bodyPatch needs something to patch: send fromRequestId (the captured request whose real response the patch is merged into) or the id of an existing rule."
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
if (rawRule.force !== true) {
|
|
693
|
-
// Address list items in the real response by their own id rather than
|
|
694
|
-
// resending the array: {results:{pikachu:{name:"test123"}}}. Expanded
|
|
695
|
-
// against the seed BEFORE the guard and before the merge, so the
|
|
696
|
-
// typed-edit rule still holds over the result. This is the only form
|
|
697
|
-
// that is correct when the caller read the response through a wire
|
|
698
|
-
// that truncated it — see packages/shared/src/sync/keyedArrays.ts.
|
|
699
|
-
let keyedError;
|
|
700
|
-
try {
|
|
701
|
-
const expanded = (0, _sharedUi.expandKeyedArrays)(JSON.parse(seed), rawRule.bodyPatch);
|
|
702
|
-
keyedError = expanded.error;
|
|
703
|
-
if (expanded.notes.length) rawRule.bodyPatch = expanded.patch;
|
|
704
|
-
} catch {
|
|
705
|
-
/* a non-JSON seed has no lists to address; leave the patch alone */
|
|
706
|
-
}
|
|
707
|
-
// See keyedArrays.ts: an added item whose id disagrees with the key
|
|
708
|
-
// it was sent under is ambiguous, and only this layer can say so.
|
|
709
|
-
if (keyedError) {
|
|
710
|
-
return {
|
|
711
|
-
ok: false,
|
|
712
|
-
error: `Refused: ${keyedError}`
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
const violations = patchViolations(rawRule, seed);
|
|
716
|
-
if (violations.length > 0) {
|
|
717
|
-
return {
|
|
718
|
-
ok: false,
|
|
719
|
-
error: (0, _sharedUi.refusalText)(violations, "response body", (0, _sharedUi.suggestedPatch)(rawRule.bodyPatch, violations)),
|
|
720
|
-
violations
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
const draft = readRuleDraft(params, base);
|
|
726
|
-
if (!draft) {
|
|
727
|
-
const wanted = params?.fromRequestId;
|
|
728
|
-
return {
|
|
729
|
-
ok: false,
|
|
730
|
-
error: wanted ? `No captured request \`${wanted}\` — it may have been cleared. Send a urlPattern instead.` : "A rule needs a non-empty urlPattern."
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
const rule = _overrideRulesStore.overrideRulesStore.upsertRule(draft);
|
|
734
|
-
if (!rule) return {
|
|
735
|
-
ok: false,
|
|
736
|
-
error: "Override rule limit reached."
|
|
737
|
-
};
|
|
738
|
-
// A rule pushed from a remote surface is meant to take effect now;
|
|
739
|
-
// leaving it dark under an OFF master switch reads as a silent failure.
|
|
740
|
-
if (rule.enabled) _overrideRulesStore.overrideRulesStore.setEnabled(true);
|
|
741
|
-
// A RECEIPT, not the rule. `fromRequestId` seeds the real response as the
|
|
742
|
-
// body, so echoing the whole rule sent 380KB back down the socket for a
|
|
743
|
-
// Pokémon payload — and no caller reads it: the desktop mirror gets the
|
|
744
|
-
// rule from the next snapshot, and MCP only needs the id to report and
|
|
745
|
-
// remove it.
|
|
746
|
-
return {
|
|
747
|
-
ok: true,
|
|
748
|
-
rule: {
|
|
749
|
-
id: rule.id,
|
|
750
|
-
urlPattern: rule.urlPattern,
|
|
751
|
-
enabled: rule.enabled,
|
|
752
|
-
kind: rule.kind,
|
|
753
|
-
status: rule.status,
|
|
754
|
-
times: rule.times,
|
|
755
|
-
bodySize: rule.body ? rule.body.length : 0
|
|
756
|
-
},
|
|
757
|
-
// A rule with no budget overrides EVERY matching request until someone
|
|
758
|
-
// deletes it, and that is rarely what a one-off ask meant. Measured on
|
|
759
|
-
// `api-sim/next-call-fails-once` ("just the next one, then let it work
|
|
760
|
-
// again"): ten of fourteen attempts left the budget off and got a rule
|
|
761
|
-
// that never expires. Saying so is not a nag — it is the difference
|
|
762
|
-
// between a caller that adds `times` on its next turn and one that walks
|
|
763
|
-
// away leaving the app permanently broken.
|
|
764
|
-
...(rule.times === undefined || rule.times === null ? {
|
|
765
|
-
lingers: `This rule has no \`times\` budget, so it overrides EVERY matching request until it is disabled or deleted (id \`${rule.id}\`). If you meant only the next one, set times:1.`
|
|
766
|
-
} : {}),
|
|
767
|
-
...inactiveStamp()
|
|
768
|
-
};
|
|
769
|
-
},
|
|
770
|
-
setOverrideRuleEnabled: params => {
|
|
771
|
-
const id = readId(params);
|
|
772
|
-
if (!id) return {
|
|
773
|
-
ok: false,
|
|
774
|
-
error: "Missing rule id."
|
|
775
|
-
};
|
|
776
|
-
_overrideRulesStore.overrideRulesStore.setRuleEnabled(id, readBool(params, "enabled"));
|
|
777
|
-
return {
|
|
778
|
-
ok: true,
|
|
779
|
-
...inactiveStamp()
|
|
780
|
-
};
|
|
781
|
-
},
|
|
782
|
-
deleteOverrideRule: params => {
|
|
783
|
-
const id = readId(params);
|
|
784
|
-
if (!id) return {
|
|
785
|
-
ok: false,
|
|
786
|
-
error: "Missing rule id."
|
|
787
|
-
};
|
|
788
|
-
_overrideRulesStore.overrideRulesStore.removeRule(id);
|
|
789
|
-
return {
|
|
790
|
-
ok: true
|
|
791
|
-
};
|
|
792
|
-
},
|
|
793
|
-
clearOverrideRules: () => {
|
|
794
|
-
_overrideRulesStore.overrideRulesStore.clearRules();
|
|
795
|
-
return {
|
|
796
|
-
ok: true
|
|
797
|
-
};
|
|
798
|
-
},
|
|
799
|
-
/** Read the rules back — hit counts included, which the snapshot coalesces. */
|
|
800
|
-
listOverrideRules: () => _overrideRulesStore.overrideRulesStore.getState(),
|
|
801
|
-
/**
|
|
802
|
-
* The full body of one rule, for the surfaces the snapshot withheld it from
|
|
803
|
-
* (see `stripLargeRuleBodies`). Fetched when the user opens the rule, which
|
|
804
|
-
* is the only moment it's needed.
|
|
805
|
-
*/
|
|
806
|
-
getOverrideRuleBody: params => {
|
|
807
|
-
const id = readId(params);
|
|
808
|
-
if (!id) return null;
|
|
809
|
-
// `getState().rules`, not `getRules()` — the latter reports nothing while
|
|
810
|
-
// the master switch is off, and a rule is perfectly editable then.
|
|
811
|
-
const rule = _overrideRulesStore.overrideRulesStore.getState().rules.find(candidate => candidate.id === id);
|
|
812
|
-
if (!rule) return null;
|
|
813
|
-
return {
|
|
814
|
-
body: rule.body ?? null
|
|
815
|
-
};
|
|
816
|
-
},
|
|
817
|
-
/** Why isn't my rule firing? Reports the engine's own view of the world. */
|
|
818
|
-
/**
|
|
819
|
-
* Is the device actually recording right now, and why not?
|
|
820
|
-
*
|
|
821
|
-
* The honest answer to an empty request list. Without this a caller can
|
|
822
|
-
* only guess, and the plausible guesses ("interception is broken on this
|
|
823
|
-
* runtime") are all wrong — the usual answer is simply that nothing is
|
|
824
|
-
* holding capture open.
|
|
825
|
-
*/
|
|
826
|
-
getCaptureStatus: () => {
|
|
827
|
-
// RECORDING is what the caller actually wants to know, and it is driven
|
|
828
|
-
// by the store's subscriber count — not by whether the interceptor is
|
|
829
|
-
// installed. Those genuinely differ: an enabled override rule pins the
|
|
830
|
-
// interceptor (`overrideRulesStore.syncKeepAlive`) without anything
|
|
831
|
-
// writing to the event store, so reporting `isNetworkListening()` as
|
|
832
|
-
// "capturing" would answer "yes" to a device recording nothing. That is
|
|
833
|
-
// the same species of misleading answer this action exists to replace.
|
|
834
|
-
const subscribers = _networkEventStore.networkEventStore.getSubscriberCounts().total;
|
|
835
|
-
|
|
836
|
-
// `isNetworkListening()` lazily CONSTRUCTS the listener, which reaches
|
|
837
|
-
// for XMLHttpRequest — absent in Node and in any headless host. A
|
|
838
|
-
// diagnostic that throws is worse than no diagnostic, so probe
|
|
839
|
-
// defensively and report what we do know either way.
|
|
840
|
-
let interceptorInstalled = null;
|
|
841
|
-
let listenerCount = null;
|
|
842
|
-
try {
|
|
843
|
-
interceptorInstalled = (0, _networkListener.isNetworkListening)();
|
|
844
|
-
listenerCount = (0, _networkListener.getNetworkListenerCount)();
|
|
845
|
-
} catch {
|
|
846
|
-
/* no interceptable runtime here */
|
|
847
|
-
}
|
|
848
|
-
// `interceptorInstalled` is the listener's own flag; `interceptorLive`
|
|
849
|
-
// is whether the interceptors are actually reachable through the global
|
|
850
|
-
// chain. installed:true + live:false = something re-assigned fetch/XHR
|
|
851
|
-
// over the interceptor (it self-heals on the next subscribe/snapshot —
|
|
852
|
-
// see ensureInterception).
|
|
853
|
-
let interceptorLive = null;
|
|
854
|
-
try {
|
|
855
|
-
interceptorLive = (0, _networkListener.isNetworkInterceptionLive)();
|
|
856
|
-
} catch {
|
|
857
|
-
/* no interceptable runtime here */
|
|
858
|
-
}
|
|
859
|
-
return {
|
|
860
|
-
capturing: subscribers > 0,
|
|
861
|
-
subscribers,
|
|
862
|
-
interceptorInstalled,
|
|
863
|
-
interceptorLive,
|
|
864
|
-
listenerCount,
|
|
865
|
-
eventCount: _networkEventStore.networkEventStore.getEvents().length
|
|
866
|
-
};
|
|
867
|
-
},
|
|
868
|
-
debugOverrides: params => {
|
|
869
|
-
const url = (params && typeof params === "object" ? params.url : undefined) ?? "https://example.com/";
|
|
870
|
-
return {
|
|
871
|
-
interceptorInstalled: (0, _networkListener.isNetworkListening)(),
|
|
872
|
-
listenerCount: (0, _networkListener.getNetworkListenerCount)(),
|
|
873
|
-
engine: (0, _engine.inspectEngine)(url, "GET"),
|
|
874
|
-
store: {
|
|
875
|
-
enabled: _overrideRulesStore.overrideRulesStore.getState().enabled,
|
|
876
|
-
ruleCount: _overrideRulesStore.overrideRulesStore.getState().rules.length,
|
|
877
|
-
rulesVisibleToEngine: _overrideRulesStore.overrideRulesStore.getRules().length
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
};
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.networkSyncAdapter=exports.SNAPSHOT_HEADER_VALUE_LIMIT=exports.SNAPSHOT_EVENTS_BUDGET=exports.SNAPSHOT_BODY_INLINE_LIMIT=void 0;var _store=require("../conditions/store");var _networkEventStore=require("../utils/networkEventStore");var _networkSavedStore=require("../utils/networkSavedStore");var _overrideRulesStore=require("../overrides/overrideRulesStore");var _presets=require("../overrides/presets");var _engine=require("../overrides/engine");var _networkListener=require("../utils/networkListener");var _sharedUi=require("@buoy-gg/shared-ui");const SNAPSHOT_BODY_INLINE_LIMIT=exports.SNAPSHOT_BODY_INLINE_LIMIT=16*1024;const SNAPSHOT_EVENTS_BUDGET=exports.SNAPSHOT_EVENTS_BUDGET=1.25*1024*1024;function shouldStripBody(e,t){if(e===void 0)return false;if((t??0)>SNAPSHOT_BODY_INLINE_LIMIT)return true;return(0,_sharedUi.isOverWireBudget)(e,SNAPSHOT_BODY_INLINE_LIMIT)}const wireCache=new WeakMap;const strippedCache=new WeakMap;function toWireEvent(e){const t=wireCache.get(e);if(t)return t;let r;try{r=buildWireEvent(e)}catch{r=buildMetadataOnly(e)}wireCache.set(e,r);return r}function buildMetadataOnly(e){const t={...e,requestData:void 0,responseData:void 0,graphqlVariables:void 0,requestBodyOmitted:e.requestData!==void 0?true:void 0,responseBodyOmitted:e.responseData!==void 0?true:void 0};let r=1024;try{r=(0,_sharedUi.approxJsonSize)(t,SNAPSHOT_EVENTS_BUDGET).bytes}catch{}return{event:t,bytes:r}}function buildWireEvent(e){const t=shouldStripBody(e.requestData,e.requestSize);const r=shouldStripBody(e.responseData,e.responseSize);const n=toWireGraphqlVariables(e.graphqlVariables);const o=toWireHeaders(e.requestHeaders);const s=toWireHeaders(e.responseHeaders);const d=t||r||n!==e.graphqlVariables||o!==e.requestHeaders||s!==e.responseHeaders;let i=e;if(d){i={...e};if(t){i.requestData=void 0;i.requestBodyOmitted=true}if(r){i.responseData=void 0;i.responseBodyOmitted=true}if(n!==e.graphqlVariables){i.graphqlVariables=n}if(o!==e.requestHeaders){i.requestHeaders=o;i.headersOmitted=true}if(s!==e.responseHeaders){i.responseHeaders=s;i.headersOmitted=true}}return{event:i,bytes:(0,_sharedUi.approxJsonSize)(i,SNAPSHOT_EVENTS_BUDGET).bytes}}function toStrippedEvent(e){const t=strippedCache.get(e);if(t)return t;const{event:r}=toWireEvent(e);const n={...r};if(n.requestData!==void 0){n.requestData=void 0;n.requestBodyOmitted=true}if(n.responseData!==void 0){n.responseData=void 0;n.responseBodyOmitted=true}if(n.graphqlVariables!==void 0){n.graphqlVariables=toWireGraphqlVariables(n.graphqlVariables,true)}const o={event:n,bytes:(0,_sharedUi.approxJsonSize)(n,SNAPSHOT_EVENTS_BUDGET).bytes};strippedCache.set(e,o);return o}function fitEventsToBudget(e){const t=new Array(e.length);let r=0;for(let n=0;n<e.length;n++){const o=toWireEvent(e[n]);if(r+o.bytes<=SNAPSHOT_EVENTS_BUDGET){t[n]=o.event;r+=o.bytes}else{const s=toStrippedEvent(e[n]);t[n]=s.event;r+=s.bytes}}return t}function toWireGraphqlVariables(e,t=false){if(!e)return e;if(!t&&!shouldStripBody(e,void 0))return e;const r={};for(const[n,o]of Object.entries(e)){if(typeof o==="string"&&o.length<=64)r[n]=o;else if(typeof o==="number"||typeof o==="boolean"){r[n]=o}}return Object.keys(r).length>0?r:void 0}const SNAPSHOT_HEADER_VALUE_LIMIT=exports.SNAPSHOT_HEADER_VALUE_LIMIT=64;function toWireHeaders(e){if(!e)return e;let t=false;const r={};for(const[n,o]of Object.entries(e)){if(typeof o!=="string")continue;if(o.length<=SNAPSHOT_HEADER_VALUE_LIMIT){r[n]=o}else{r[n]=`${o.slice(0,SNAPSHOT_HEADER_VALUE_LIMIT)}\u2026 [${o.length-SNAPSHOT_HEADER_VALUE_LIMIT} more]`;t=true}}return t?r:e}const stripLargeRuleBodies=e=>{let t=false;const r=e.rules.map(n=>{if(!n.body||n.body.length<=SNAPSHOT_BODY_INLINE_LIMIT){return n}t=true;return{...n,body:void 0,bodyOmitted:true}});return t?{...e,rules:r}:e};const resolveEvent=e=>_networkEventStore.networkEventStore.getEventById(e)??_networkSavedStore.networkSavedStore.getEventById(e);const readId=e=>e&&typeof e==="object"?e.id:void 0;const readBool=(e,t)=>!!(e&&typeof e==="object"&&e[t]);const inactiveStamp=()=>(0,_engine.overridesCanApply)()?{}:{active:false,inactiveReason:_engine.OVERRIDES_INACTIVE_REASON};const VALID_KINDS=["respond","fail","delay"];function readRuleDraft(e,t){if(!e||typeof e!=="object")return null;const r=e.rule??e;if(!r||typeof r!=="object")return null;const n=r;const o=typeof n.urlPattern==="string"&&n.urlPattern.trim()?n.urlPattern.trim():t?.urlPattern??"";if(!o)return null;const s=VALID_KINDS.includes(n.kind)?n.kind:t?.kind??"respond";return{id:typeof n.id==="string"?n.id:void 0,enabled:n.enabled!==false,name:typeof n.name==="string"?n.name:t?.name,urlPattern:o,methods:Array.isArray(n.methods)?n.methods.filter(d=>typeof d==="string").map(d=>d.toUpperCase()):t?.methods,requireHeader:typeof n.requireHeader==="string"&&n.requireHeader.trim()?n.requireHeader.trim():void 0,kind:s,status:typeof n.status==="number"?n.status:t?.status,statusText:typeof n.statusText==="string"?n.statusText:t?.statusText,headers:n.headers&&typeof n.headers==="object"?n.headers:t?.headers,body:patchedBody(n,t),failKind:n.failKind==="timeout"?"timeout":void 0,delayMs:typeof n.delayMs==="number"?n.delayMs:void 0,times:typeof n.times==="number"?n.times:null,alternate:n.alternate===true}}const isPlainObject=e=>!!e&&typeof e==="object"&&!Array.isArray(e);function deepMerge(e,t,r="",n={shrunk:[],breaks:[]}){if(Array.isArray(e)&&Array.isArray(t)){if(t.length>0&&t.length<e.length){n.shrunk.push({path:r||"(root)",have:e.length,sent:t.length})}return t}if(!isPlainObject(e)||!isPlainObject(t))return t;const o={...e};for(const[s,d]of Object.entries(t)){const i=r?`${r}.${s}`:s;if(s in e){const u=(0,_sharedUi.shapeKind)(e[s]);const l=(0,_sharedUi.shapeKind)(d);if((u==="array"||u==="object")&&u!==l){n.breaks.push({path:i,from:u,to:l})}o[s]=deepMerge(e[s],d,i,n)}else{o[s]=d}}return o}function patchViolations(e,t){if(e.body!=null||!isPlainObject(e.bodyPatch)||typeof t!=="string")return[];try{return(0,_sharedUi.typedEditViolations)(JSON.parse(t),e.bodyPatch,{noun:"response body"})}catch{return[]}}function patchedBody(e,t){if(typeof e.body==="string")return e.body;if(e.body!=null)return JSON.stringify(e.body,null,2);const r=t?.body??(typeof e.id==="string"?_overrideRulesStore.overrideRulesStore.getState().rules.find(n=>n.id===e.id)?.body:void 0);if(isPlainObject(e.bodyPatch)&&typeof r==="string"){try{return JSON.stringify(deepMerge(JSON.parse(r),e.bodyPatch),null,2)}catch{return r}}return r}function existingRule(e){if(!e||typeof e!=="object")return null;const t=e.rule??e;if(!t||typeof t!=="object")return null;const r=t.id;if(typeof r!=="string")return null;const n=_overrideRulesStore.overrideRulesStore.getState().rules.find(o=>o.id===r);return n?{...n}:null}function preservedBody(e){if(!e||typeof e!=="object")return null;const t=e.rule??e;if(!t||typeof t!=="object")return null;const r=t;if(r.bodyOmitted!==true||typeof r.id!=="string")return null;const n=_overrideRulesStore.overrideRulesStore.getState().rules.find(o=>o.id===r.id);return n?{body:n.body}:null}function baseFromRequest(e){if(!e||typeof e!=="object")return null;const t=e.fromRequestId;if(typeof t!=="string"||!t)return null;const r=resolveEvent(t);return r?(0,_presets.draftFromEvent)(r):null}const networkSyncAdapter=exports.networkSyncAdapter={dispose:()=>{_overrideRulesStore.overrideRulesStore.setEnabled(false)},version:6,getSnapshot:()=>{_networkEventStore.networkEventStore.ensureInterception();return{conditions:{version:1,runtimeId:_store.networkConditionsRuntimeId,profile:(0,_store.getNetworkCondition)(),available:(0,_store.conditionsCanApply)()},events:fitEventsToBudget(_networkEventStore.networkEventStore.getEvents()),overrides:stripLargeRuleBodies(_overrideRulesStore.overrideRulesStore.getState()),saved:_networkSavedStore.networkSavedStore.getRecords().map(e=>({...e,event:toWireEvent(e.event).event}))}},subscribe:e=>{const t=_networkEventStore.networkEventStore.subscribeToEvents(e);const r=(0,_store.subscribeNetworkConditions)(e);const n=_networkSavedStore.networkSavedStore.subscribe(e);const o=_overrideRulesStore.overrideRulesStore.subscribe(e);return()=>{r();t();n();o()}},actions:{getNetworkConditions:()=>({runtimeId:_store.networkConditionsRuntimeId,profile:(0,_store.getNetworkCondition)(),available:(0,_store.conditionsCanApply)()}),setNetworkConditions:e=>{const t=e&&typeof e==="object"?e.profile:void 0;return{runtimeId:_store.networkConditionsRuntimeId,profile:(0,_store.setNetworkCondition)(t),available:(0,_store.conditionsCanApply)()}},clearEvents:()=>{_networkEventStore.networkEventStore.clearEvents()},getEventBody:e=>{const t=readId(e);if(!t)return null;const r=resolveEvent(t);if(!r)return null;return{url:r.url??null,method:r.method??null,status:r.status??null,override:r.override??null,requestData:r.requestData??null,responseData:r.responseData??null,requestHeaders:r.requestHeaders??null,responseHeaders:r.responseHeaders??null}},setPinned:e=>{const t=readId(e);if(!t)return null;const r=resolveEvent(t);if(!r)return null;const n=readBool(e,"pinned");const o=(0,_networkSavedStore.flagsForEventId)(_networkSavedStore.networkSavedStore.getState(),t).pinned;if(o===n)return{ok:true};return _networkSavedStore.networkSavedStore.togglePin(r)},setSaved:e=>{const t=readId(e);if(!t)return null;const r=resolveEvent(t);if(!r)return null;const n=readBool(e,"saved");const o=(0,_networkSavedStore.flagsForEventId)(_networkSavedStore.networkSavedStore.getState(),t).saved;if(o===n)return{ok:true};return _networkSavedStore.networkSavedStore.toggleSave(r)},removeSavedRecord:e=>{const t=e&&typeof e==="object"?e.key:void 0;if(!t)return null;_networkSavedStore.networkSavedStore.remove(t);return{ok:true}},clearSavedRequests:()=>{_networkSavedStore.networkSavedStore.clearSaved();return{ok:true}},clearPinnedRequests:()=>{_networkSavedStore.networkSavedStore.clearPinned();return{ok:true}},setOverridesEnabled:e=>{_overrideRulesStore.overrideRulesStore.setEnabled(readBool(e,"enabled"));return{ok:true,enabled:_overrideRulesStore.overrideRulesStore.getState().enabled,...inactiveStamp()}},upsertOverrideRule:e=>{const t=baseFromRequest(e)??preservedBody(e)??existingRule(e);const r=e?.rule??e;if(r&&typeof r.bodyPath==="string"&&r.body==null){const s=t?.body??(typeof r.id==="string"?_overrideRulesStore.overrideRulesStore.getState().rules.find(u=>u.id===r.id)?.body:void 0);if(typeof s!=="string"){return{ok:false,error:"bodyPath needs something to patch: send fromRequestId (the captured request whose real response it addresses) or the id of an existing rule."}}if(r.bodyValue===void 0){return{ok:false,error:`Refused: sent \`bodyPath\` without \`bodyValue\`. Say what to set "${r.bodyPath}" to.`}}let d;try{d=JSON.parse(s)}catch{return{ok:false,error:"The captured response body is not JSON, so it cannot be addressed by path. Send a whole `body` instead."}}const i=(0,_sharedUi.patchFromPath)(d,r.bodyPath,r.bodyValue);if(!i.ok)return{ok:false,error:`Refused: ${i.error}`};r.bodyPatch=i.patch;delete r.bodyPath;delete r.bodyValue}if(r&&isPlainObject(r.bodyPatch)&&r.body==null){const s=t?.body??(typeof r.id==="string"?_overrideRulesStore.overrideRulesStore.getState().rules.find(d=>d.id===r.id)?.body:void 0);if(typeof s!=="string"){return{ok:false,error:"bodyPatch needs something to patch: send fromRequestId (the captured request whose real response the patch is merged into) or the id of an existing rule."}}if(r.force!==true){let d;try{const u=(0,_sharedUi.expandKeyedArrays)(JSON.parse(s),r.bodyPatch);d=u.error;if(u.notes.length)r.bodyPatch=u.patch}catch{}if(d){return{ok:false,error:`Refused: ${d}`}}const i=patchViolations(r,s);if(i.length>0){return{ok:false,error:(0,_sharedUi.refusalText)(i,"response body",(0,_sharedUi.suggestedPatch)(r.bodyPatch,i)),violations:i}}}}const n=readRuleDraft(e,t);if(!n){const s=e?.fromRequestId;return{ok:false,error:s?`No captured request \`${s}\` \u2014 it may have been cleared. Send a urlPattern instead.`:"A rule needs a non-empty urlPattern."}}const o=_overrideRulesStore.overrideRulesStore.upsertRule(n);if(!o)return{ok:false,error:"Override rule limit reached."};if(o.enabled)_overrideRulesStore.overrideRulesStore.setEnabled(true);return{ok:true,rule:{id:o.id,urlPattern:o.urlPattern,enabled:o.enabled,kind:o.kind,status:o.status,times:o.times,bodySize:o.body?o.body.length:0},...o.times===void 0||o.times===null?{lingers:`This rule has no \`times\` budget, so it overrides EVERY matching request until it is disabled or deleted (id \`${o.id}\`). If you meant only the next one, set times:1.`}:{},...inactiveStamp()}},setOverrideRuleEnabled:e=>{const t=readId(e);if(!t)return{ok:false,error:"Missing rule id."};_overrideRulesStore.overrideRulesStore.setRuleEnabled(t,readBool(e,"enabled"));return{ok:true,...inactiveStamp()}},deleteOverrideRule:e=>{const t=readId(e);if(!t)return{ok:false,error:"Missing rule id."};_overrideRulesStore.overrideRulesStore.removeRule(t);return{ok:true}},clearOverrideRules:()=>{_overrideRulesStore.overrideRulesStore.clearRules();return{ok:true}},listOverrideRules:()=>_overrideRulesStore.overrideRulesStore.getState(),getOverrideRuleBody:e=>{const t=readId(e);if(!t)return null;const r=_overrideRulesStore.overrideRulesStore.getState().rules.find(n=>n.id===t);if(!r)return null;return{body:r.body??null}},getCaptureStatus:()=>{const e=_networkEventStore.networkEventStore.getSubscriberCounts().total;let t=null;let r=null;try{t=(0,_networkListener.isNetworkListening)();r=(0,_networkListener.getNetworkListenerCount)()}catch{}let n=null;try{n=(0,_networkListener.isNetworkInterceptionLive)()}catch{}return{capturing:e>0,subscribers:e,interceptorInstalled:t,interceptorLive:n,listenerCount:r,eventCount:_networkEventStore.networkEventStore.getEvents().length}},debugOverrides:e=>{const t=(e&&typeof e==="object"?e.url:void 0)??"https://example.com/";return{interceptorInstalled:(0,_networkListener.isNetworkListening)(),listenerCount:(0,_networkListener.getNetworkListenerCount)(),engine:(0,_engine.inspectEngine)(t,"GET"),store:{enabled:_overrideRulesStore.overrideRulesStore.getState().enabled,ruleCount:_overrideRulesStore.overrideRulesStore.getState().rules.length,rulesVisibleToEngine:_overrideRulesStore.overrideRulesStore.getRules().length}}}}};
|