@buoy-gg/network 7.0.13 → 7.0.15
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/lib/commonjs/index.js +39 -0
- package/lib/commonjs/network/components/NetworkEventDetailView.js +30 -3
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +4 -0
- package/lib/commonjs/network/components/NetworkHeaderLive.js +26 -6
- package/lib/commonjs/network/components/NetworkHeaderMenu.js +183 -0
- package/lib/commonjs/network/components/NetworkListScreen.js +126 -5
- package/lib/commonjs/network/components/NetworkModal.js +136 -29
- package/lib/commonjs/network/components/NetworkNumberPrompt.js +178 -0
- package/lib/commonjs/network/components/NetworkOverrideEditor.js +797 -0
- package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +102 -0
- package/lib/commonjs/network/components/NetworkOverridesButton.js +88 -0
- package/lib/commonjs/network/components/NetworkOverridesScreen.js +489 -0
- package/lib/commonjs/network/components/response-editor/Explorer.js +973 -0
- package/lib/commonjs/network/components/response-editor/deleteNestedDataByPath.js +56 -0
- package/lib/commonjs/network/components/response-editor/editorContext.js +33 -0
- package/lib/commonjs/network/components/response-editor/updateNestedDataByPath.js +61 -0
- package/lib/commonjs/network/hooks/useOverrideRules.js +36 -0
- package/lib/commonjs/network/overrides/applyToFetch.js +80 -0
- package/lib/commonjs/network/overrides/applyToXhr.js +190 -0
- package/lib/commonjs/network/overrides/engine.js +70 -0
- package/lib/commonjs/network/overrides/matchRule.js +81 -0
- package/lib/commonjs/network/overrides/overrideRulesStore.js +550 -0
- package/lib/commonjs/network/overrides/presets.js +70 -0
- package/lib/commonjs/network/overrides/resolveOverride.js +158 -0
- package/lib/commonjs/network/overrides/types.js +85 -0
- package/lib/commonjs/network/sync/networkSyncAdapter.js +230 -1
- package/lib/commonjs/network/types/index.js +5 -1
- package/lib/commonjs/network/utils/networkEventStore.js +52 -6
- package/lib/commonjs/network/utils/networkListener.js +90 -10
- package/lib/commonjs/preset.js +11 -0
- package/lib/module/index.js +15 -0
- package/lib/module/network/components/NetworkEventDetailView.js +31 -4
- package/lib/module/network/components/NetworkEventItemCompact.js +5 -1
- package/lib/module/network/components/NetworkHeaderLive.js +26 -6
- package/lib/module/network/components/NetworkHeaderMenu.js +177 -0
- package/lib/module/network/components/NetworkListScreen.js +127 -6
- package/lib/module/network/components/NetworkModal.js +138 -31
- package/lib/module/network/components/NetworkNumberPrompt.js +174 -0
- package/lib/module/network/components/NetworkOverrideEditor.js +794 -0
- package/lib/module/network/components/NetworkOverrideHeaderButton.js +99 -0
- package/lib/module/network/components/NetworkOverridesButton.js +85 -0
- package/lib/module/network/components/NetworkOverridesScreen.js +485 -0
- package/lib/module/network/components/response-editor/Explorer.js +972 -0
- package/lib/module/network/components/response-editor/deleteNestedDataByPath.js +51 -0
- package/lib/module/network/components/response-editor/editorContext.js +29 -0
- package/lib/module/network/components/response-editor/updateNestedDataByPath.js +56 -0
- package/lib/module/network/hooks/useOverrideRules.js +32 -0
- package/lib/module/network/overrides/applyToFetch.js +72 -0
- package/lib/module/network/overrides/applyToXhr.js +185 -0
- package/lib/module/network/overrides/engine.js +64 -0
- package/lib/module/network/overrides/matchRule.js +75 -0
- package/lib/module/network/overrides/overrideRulesStore.js +547 -0
- package/lib/module/network/overrides/presets.js +65 -0
- package/lib/module/network/overrides/resolveOverride.js +152 -0
- package/lib/module/network/overrides/types.js +80 -0
- package/lib/module/network/sync/networkSyncAdapter.js +230 -1
- package/lib/module/network/types/index.js +3 -1
- package/lib/module/network/utils/networkEventStore.js +52 -6
- package/lib/module/network/utils/networkListener.js +91 -10
- package/lib/module/preset.js +13 -1
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +6 -0
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts +8 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderMenu.d.ts +46 -0
- package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkNumberPrompt.d.ts +33 -0
- package/lib/typescript/network/components/NetworkNumberPrompt.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverrideEditor.d.ts +17 -0
- package/lib/typescript/network/components/NetworkOverrideEditor.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts +27 -0
- package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverridesButton.d.ts +17 -0
- package/lib/typescript/network/components/NetworkOverridesButton.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +29 -0
- package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/Explorer.d.ts +17 -0
- package/lib/typescript/network/components/response-editor/Explorer.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts +9 -0
- package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/editorContext.d.ts +29 -0
- package/lib/typescript/network/components/response-editor/editorContext.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts +10 -0
- package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useOverrideRules.d.ts +29 -0
- package/lib/typescript/network/hooks/useOverrideRules.d.ts.map +1 -0
- package/lib/typescript/network/overrides/__tests__/applyToFetch.test.d.ts +16 -0
- package/lib/typescript/network/overrides/__tests__/applyToFetch.test.d.ts.map +1 -0
- package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts +19 -0
- package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -0
- package/lib/typescript/network/overrides/__tests__/resolveOverride.test.d.ts +22 -0
- package/lib/typescript/network/overrides/__tests__/resolveOverride.test.d.ts.map +1 -0
- package/lib/typescript/network/overrides/applyToFetch.d.ts +50 -0
- package/lib/typescript/network/overrides/applyToFetch.d.ts.map +1 -0
- package/lib/typescript/network/overrides/applyToXhr.d.ts +43 -0
- package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -0
- package/lib/typescript/network/overrides/engine.d.ts +47 -0
- package/lib/typescript/network/overrides/engine.d.ts.map +1 -0
- package/lib/typescript/network/overrides/matchRule.d.ts +22 -0
- package/lib/typescript/network/overrides/matchRule.d.ts.map +1 -0
- package/lib/typescript/network/overrides/overrideRulesStore.d.ts +205 -0
- package/lib/typescript/network/overrides/overrideRulesStore.d.ts.map +1 -0
- package/lib/typescript/network/overrides/presets.d.ts +38 -0
- package/lib/typescript/network/overrides/presets.d.ts.map +1 -0
- package/lib/typescript/network/overrides/resolveOverride.d.ts +46 -0
- package/lib/typescript/network/overrides/resolveOverride.d.ts.map +1 -0
- package/lib/typescript/network/overrides/types.d.ts +169 -0
- package/lib/typescript/network/overrides/types.d.ts.map +1 -0
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts +73 -0
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/network/types/index.d.ts +10 -0
- package/lib/typescript/network/types/index.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkEventStore.d.ts +14 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkListener.d.ts +3 -0
- package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
- package/lib/typescript/preset.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -4,11 +4,29 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.stopNetworkListener = exports.startNetworkListener = exports.removeAllNetworkListeners = exports.networkListener = exports.isNetworkListening = exports.getNetworkListenerCount = exports.addNetworkListener = void 0;
|
|
7
|
+
var _engine = require("../overrides/engine");
|
|
8
|
+
var _applyToXhr = require("../overrides/applyToXhr");
|
|
9
|
+
var _applyToFetch = require("../overrides/applyToFetch");
|
|
10
|
+
var _types = require("../overrides/types");
|
|
7
11
|
/**
|
|
8
12
|
* Network listener using Reactotron-style event pattern
|
|
9
13
|
* Simple and reliable network interception for React Native
|
|
10
14
|
*/
|
|
11
15
|
|
|
16
|
+
/**
|
|
17
|
+
* True only while `originalFetch` is being CALLED (not awaited).
|
|
18
|
+
*
|
|
19
|
+
* Overrides hook both `fetch` and `XMLHttpRequest`, because Expo's native
|
|
20
|
+
* `expo/fetch` bypasses XHR while bare RN's whatwg-fetch polyfill runs straight
|
|
21
|
+
* through it. On bare RN that means one `fetch()` would otherwise be overridden
|
|
22
|
+
* twice — a doubled delay and a doubled hit count.
|
|
23
|
+
*
|
|
24
|
+
* whatwg-fetch creates and sends its XHR synchronously inside the Promise
|
|
25
|
+
* executor, so a flag raised across the call expression alone is enough to
|
|
26
|
+
* cover the inner `send()` without leaking across the await.
|
|
27
|
+
*/
|
|
28
|
+
let insideOverriddenFetch = false;
|
|
29
|
+
|
|
12
30
|
// Extended XMLHttpRequest interface for monkey-patching
|
|
13
31
|
|
|
14
32
|
// Event types for network operations
|
|
@@ -203,7 +221,7 @@ class NetworkListener {
|
|
|
203
221
|
* Handle XMLHttpRequest response processing
|
|
204
222
|
* This method processes the response and emits appropriate events
|
|
205
223
|
*/
|
|
206
|
-
handleXHRResponse(xhr, requestId, cleanUrl, method, requestHeaders, requestData, params, startTime, isError = false, clientType = "xhr") {
|
|
224
|
+
handleXHRResponse(xhr, requestId, cleanUrl, method, requestHeaders, requestData, params, startTime, isError = false, clientType = "xhr", override) {
|
|
207
225
|
const duration = startTime ? Date.now() - startTime : 0;
|
|
208
226
|
|
|
209
227
|
// XHR's duration was ALREADY total (measured at readyState 4 / load), so
|
|
@@ -232,7 +250,8 @@ class NetworkListener {
|
|
|
232
250
|
},
|
|
233
251
|
error: {
|
|
234
252
|
message: isError ? "Request failed" : "Network error or request aborted"
|
|
235
|
-
}
|
|
253
|
+
},
|
|
254
|
+
override
|
|
236
255
|
});
|
|
237
256
|
return;
|
|
238
257
|
}
|
|
@@ -371,7 +390,8 @@ class NetworkListener {
|
|
|
371
390
|
headers: responseHeaders,
|
|
372
391
|
body,
|
|
373
392
|
size: responseSize
|
|
374
|
-
}
|
|
393
|
+
},
|
|
394
|
+
override
|
|
375
395
|
});
|
|
376
396
|
} else {
|
|
377
397
|
this.emit({
|
|
@@ -396,7 +416,8 @@ class NetworkListener {
|
|
|
396
416
|
},
|
|
397
417
|
error: {
|
|
398
418
|
message: `HTTP ${xhr.status}: ${xhr.statusText}`
|
|
399
|
-
}
|
|
419
|
+
},
|
|
420
|
+
override
|
|
400
421
|
});
|
|
401
422
|
}
|
|
402
423
|
}
|
|
@@ -467,6 +488,18 @@ class NetworkListener {
|
|
|
467
488
|
// Determine client type from X-Request-Client header or default to fetch
|
|
468
489
|
const clientType = requestHeaders["X-Request-Client"] || requestHeaders["x-request-client"] || "fetch";
|
|
469
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Response overrides, fetch side.
|
|
493
|
+
*
|
|
494
|
+
* Needed even though `send` below also resolves them: Expo installs a
|
|
495
|
+
* NATIVE `expo/fetch` as `globalThis.fetch`, which never constructs an
|
|
496
|
+
* XMLHttpRequest, so the XHR hook alone would never see these requests.
|
|
497
|
+
* Skipped when `canSynthesizeResponse()` is false so a runtime without a
|
|
498
|
+
* `Response` global degrades to a real request rather than a broken one.
|
|
499
|
+
*/
|
|
500
|
+
const outcome = (0, _applyToFetch.canSynthesizeResponse)() ? (0, _engine.overrideForRequest)(url, method) : null;
|
|
501
|
+
const override = outcome ? (0, _types.markForOutcome)(outcome) : undefined;
|
|
502
|
+
|
|
470
503
|
// Emit request event
|
|
471
504
|
self.emit({
|
|
472
505
|
type: "request",
|
|
@@ -479,10 +512,28 @@ class NetworkListener {
|
|
|
479
512
|
data: requestData,
|
|
480
513
|
params: params || undefined,
|
|
481
514
|
client: clientType
|
|
482
|
-
}
|
|
515
|
+
},
|
|
516
|
+
override
|
|
483
517
|
});
|
|
484
518
|
try {
|
|
485
|
-
|
|
519
|
+
if (outcome) await (0, _applyToFetch.sleep)(outcome.delayMs);
|
|
520
|
+
if (outcome?.kind === "fail") throw (0, _applyToFetch.synthesizeFetchError)(outcome);
|
|
521
|
+
let response;
|
|
522
|
+
if (outcome?.kind === "respond") {
|
|
523
|
+
response = (0, _applyToFetch.synthesizeResponse)(url, outcome);
|
|
524
|
+
} else {
|
|
525
|
+
// The flag spans the CALL only — see `insideOverriddenFetch`. It has
|
|
526
|
+
// to be cleared before the await, or it would still be raised for
|
|
527
|
+
// every unrelated request that starts while this one is in flight.
|
|
528
|
+
insideOverriddenFetch = outcome != null;
|
|
529
|
+
let pending;
|
|
530
|
+
try {
|
|
531
|
+
pending = self.originalFetch(input, init);
|
|
532
|
+
} finally {
|
|
533
|
+
insideOverriddenFetch = false;
|
|
534
|
+
}
|
|
535
|
+
response = await pending;
|
|
536
|
+
}
|
|
486
537
|
// A fetch promise resolves when the HEADERS arrive, not the body — so
|
|
487
538
|
// this is time-to-first-byte, and it is where `duration` used to stop.
|
|
488
539
|
// That silently excluded download time, making an identical request
|
|
@@ -551,7 +602,8 @@ class NetworkListener {
|
|
|
551
602
|
headers: responseHeaders,
|
|
552
603
|
body,
|
|
553
604
|
size: responseSize
|
|
554
|
-
}
|
|
605
|
+
},
|
|
606
|
+
override
|
|
555
607
|
});
|
|
556
608
|
return response;
|
|
557
609
|
} catch (error) {
|
|
@@ -574,7 +626,8 @@ class NetworkListener {
|
|
|
574
626
|
error: {
|
|
575
627
|
message: error instanceof Error ? error.message : "Network error",
|
|
576
628
|
stack: error instanceof Error ? error.stack : undefined
|
|
577
|
-
}
|
|
629
|
+
},
|
|
630
|
+
override
|
|
578
631
|
});
|
|
579
632
|
throw error;
|
|
580
633
|
}
|
|
@@ -641,6 +694,22 @@ class NetworkListener {
|
|
|
641
694
|
const looksLikeAxios = acceptHeader.includes("application/json") && acceptHeader.includes("text/plain") && acceptHeader.includes("*/*");
|
|
642
695
|
const clientType = explicitClient || (looksLikeAxios ? "axios" : "xhr");
|
|
643
696
|
|
|
697
|
+
/**
|
|
698
|
+
* Response overrides.
|
|
699
|
+
*
|
|
700
|
+
* This is the layer that covers `axios` and raw XMLHttpRequest. It also
|
|
701
|
+
* covers `fetch` on BARE React Native, where the whatwg-fetch polyfill
|
|
702
|
+
* runs through XHR — which is why the fetch wrapper raises
|
|
703
|
+
* `insideOverriddenFetch` around its pass-through, so one request can't
|
|
704
|
+
* be overridden twice.
|
|
705
|
+
*
|
|
706
|
+
* Matching uses the raw `url` (query string included), which is what the
|
|
707
|
+
* developer sees in the list and writes into a rule. No-ops in production
|
|
708
|
+
* builds; see `overrideForRequest`.
|
|
709
|
+
*/
|
|
710
|
+
const outcome = insideOverriddenFetch ? null : (0, _engine.overrideForRequest)(url, method);
|
|
711
|
+
const override = outcome ? (0, _types.markForOutcome)(outcome) : undefined;
|
|
712
|
+
|
|
644
713
|
// Emit request event
|
|
645
714
|
self.emit({
|
|
646
715
|
type: "request",
|
|
@@ -653,7 +722,8 @@ class NetworkListener {
|
|
|
653
722
|
data: requestData,
|
|
654
723
|
params: params || undefined,
|
|
655
724
|
client: clientType
|
|
656
|
-
}
|
|
725
|
+
},
|
|
726
|
+
override
|
|
657
727
|
});
|
|
658
728
|
|
|
659
729
|
// Track if we've already processed this request to avoid duplicate events
|
|
@@ -661,7 +731,7 @@ class NetworkListener {
|
|
|
661
731
|
const processResponse = (isError = false) => {
|
|
662
732
|
if (processed) return;
|
|
663
733
|
processed = true;
|
|
664
|
-
self.handleXHRResponse(this, requestId || "unknown", cleanUrl, method, requestHeaders, requestData, params, startTime || 0, isError, clientType);
|
|
734
|
+
self.handleXHRResponse(this, requestId || "unknown", cleanUrl, method, requestHeaders, requestData, params, startTime || 0, isError, clientType, override);
|
|
665
735
|
// Clean up event listeners after processing to prevent memory leaks
|
|
666
736
|
cleanup();
|
|
667
737
|
};
|
|
@@ -708,6 +778,16 @@ class NetworkListener {
|
|
|
708
778
|
this.addEventListener("error", errorListener);
|
|
709
779
|
this.addEventListener("abort", abortListener);
|
|
710
780
|
this.addEventListener("readystatechange", readyStateListener);
|
|
781
|
+
if (outcome) {
|
|
782
|
+
// Listeners are attached above FIRST: `runOverride` drives the same
|
|
783
|
+
// readyState transitions a real response would, and the interceptor has
|
|
784
|
+
// to see them (readyState 2 is where TTFB is stamped).
|
|
785
|
+
const handled = (0, _applyToXhr.runOverride)(this, url, outcome, () => self.originalXHRSend.call(this, data));
|
|
786
|
+
// `handled` is false only where this isn't React Native's own
|
|
787
|
+
// XMLHttpRequest and can't be driven by hand. Fall through and send for
|
|
788
|
+
// real rather than dropping the request on the floor.
|
|
789
|
+
if (handled) return;
|
|
790
|
+
}
|
|
711
791
|
return self.originalXHRSend.call(this, data);
|
|
712
792
|
};
|
|
713
793
|
this.isListening = true;
|
package/lib/commonjs/preset.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.createNetworkTool = createNetworkTool;
|
|
|
7
7
|
exports.networkToolPreset = void 0;
|
|
8
8
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
9
|
var _NetworkModal = require("./network/components/NetworkModal");
|
|
10
|
+
var _overrideRulesStore = require("./network/overrides/overrideRulesStore");
|
|
10
11
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
12
|
/**
|
|
12
13
|
* Pre-configured network monitoring tool for FloatingDevTools
|
|
@@ -25,6 +26,16 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
25
26
|
* ```
|
|
26
27
|
*/
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Load persisted response-override rules as soon as the tool is registered.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately at import time rather than when the Network modal mounts: an
|
|
33
|
+
* override has to fire from app boot, with the tool closed and nothing
|
|
34
|
+
* capturing — that is the whole point of persisting rules ("force this endpoint
|
|
35
|
+
* to 500, then restart and watch the boot path"). Hydrating also re-installs
|
|
36
|
+
* the interceptor if any rule is still active. One storage read, dev-only.
|
|
37
|
+
*/void _overrideRulesStore.overrideRulesStore.ensureLoaded();
|
|
38
|
+
|
|
28
39
|
/**
|
|
29
40
|
* Pre-configured network monitoring tool for FloatingDevTools.
|
|
30
41
|
* Includes:
|
package/lib/module/index.js
CHANGED
|
@@ -56,6 +56,21 @@ export { formatBytes, formatDuration, formatHttpStatus } from "./network/utils/f
|
|
|
56
56
|
// TYPES (For TypeScript users)
|
|
57
57
|
// =============================================================================
|
|
58
58
|
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// RESPONSE OVERRIDES
|
|
61
|
+
// Force a status, body, failure or delay on matching requests — Buoy's answer
|
|
62
|
+
// to Chrome DevTools' Local Overrides, plus the status/latency/failure control
|
|
63
|
+
// Chrome doesn't have.
|
|
64
|
+
// =============================================================================
|
|
65
|
+
export { overrideRulesStore
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @internal — the desktop dashboard puts the rule store in remote-mirror mode
|
|
69
|
+
* so edits made there are forwarded to the device instead of stored locally.
|
|
70
|
+
*/ } from "./network/overrides/overrideRulesStore";
|
|
71
|
+
export { draftFromEvent } from "./network/overrides/presets";
|
|
72
|
+
export { MAX_RULES as MAX_OVERRIDE_RULES, FREE_TIER_RULE_LIMIT as FREE_TIER_OVERRIDE_LIMIT, STATUS_MIN as OVERRIDE_STATUS_MIN, STATUS_MAX as OVERRIDE_STATUS_MAX } from "./network/overrides/types";
|
|
73
|
+
|
|
59
74
|
// =============================================================================
|
|
60
75
|
// SHARED IGNORED-PATTERN FILTERING
|
|
61
76
|
// Re-exported from @buoy-gg/shared-ui so consumers wiring the Network detail
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useMemo, memo } from "react";
|
|
4
4
|
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
|
|
5
|
-
import { Clock, Upload, Download, AlertCircle, ChevronDown, ChevronUp, Lock, Unlock, FileJson, Filter, Globe, Link, Copy, InlineCopyButton, macOSColors, useFeatureGate, UpgradeModal } from "@buoy-gg/shared-ui";
|
|
5
|
+
import { Clock, Upload, Download, AlertCircle, ChevronDown, ChevronUp, Lock, Unlock, FileJson, Filter, Globe, Link, Copy, FlaskConical, InlineCopyButton, macOSColors, useFeatureGate, UpgradeModal } from "@buoy-gg/shared-ui";
|
|
6
6
|
import { formatBytes, formatDuration, formatHttpStatus } from "../utils/formatting";
|
|
7
7
|
import { formatRelativeTime } from "@buoy-gg/shared-ui";
|
|
8
8
|
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
@@ -73,8 +73,11 @@ const TimingPhases = ({
|
|
|
73
73
|
children: /*#__PURE__*/_jsx(View, {
|
|
74
74
|
style: [styles.phaseBar, {
|
|
75
75
|
backgroundColor: phase.color,
|
|
76
|
-
// A floor so a 1ms phase is still a visible tick
|
|
77
|
-
|
|
76
|
+
// A floor so a 1ms phase is still a visible tick, and a
|
|
77
|
+
// ceiling because `total` and the phases are measured at
|
|
78
|
+
// different points — ttfb can exceed event.duration, which
|
|
79
|
+
// would push the bar past its track and over the label.
|
|
80
|
+
width: `${Math.min(100, Math.max(1, phase.value / span * 100))}%`
|
|
78
81
|
}]
|
|
79
82
|
})
|
|
80
83
|
}), /*#__PURE__*/_jsx(Text, {
|
|
@@ -442,6 +445,15 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
|
|
|
442
445
|
style: styles.httpDurationText,
|
|
443
446
|
children: formatDuration(event.duration)
|
|
444
447
|
})]
|
|
448
|
+
}) : null, event.override ? /*#__PURE__*/_jsxs(View, {
|
|
449
|
+
style: styles.overrideBadge,
|
|
450
|
+
children: [/*#__PURE__*/_jsx(FlaskConical, {
|
|
451
|
+
size: 10,
|
|
452
|
+
color: macOSColors.semantic.warning
|
|
453
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
454
|
+
style: styles.overrideBadgeText,
|
|
455
|
+
children: event.override.ruleName || "Overridden"
|
|
456
|
+
})]
|
|
445
457
|
}) : null, isPro ? /*#__PURE__*/_jsx(InlineCopyButton
|
|
446
458
|
// LAZY, not `getFullRequestDetails()`. CopyButton resolves a
|
|
447
459
|
// function value at press time; calling it here instead
|
|
@@ -795,6 +807,20 @@ const styles = StyleSheet.create({
|
|
|
795
807
|
color: macOSColors.text.secondary,
|
|
796
808
|
fontSize: 11
|
|
797
809
|
},
|
|
810
|
+
overrideBadge: {
|
|
811
|
+
flexDirection: "row",
|
|
812
|
+
alignItems: "center",
|
|
813
|
+
gap: 4,
|
|
814
|
+
paddingHorizontal: 6,
|
|
815
|
+
paddingVertical: 3,
|
|
816
|
+
borderRadius: 4,
|
|
817
|
+
backgroundColor: macOSColors.semantic.warningBackground
|
|
818
|
+
},
|
|
819
|
+
overrideBadgeText: {
|
|
820
|
+
color: macOSColors.semantic.warning,
|
|
821
|
+
fontSize: 10,
|
|
822
|
+
fontWeight: "700"
|
|
823
|
+
},
|
|
798
824
|
copyFullButton: {
|
|
799
825
|
marginLeft: "auto",
|
|
800
826
|
flexShrink: 0,
|
|
@@ -947,7 +973,8 @@ const styles = StyleSheet.create({
|
|
|
947
973
|
height: 7,
|
|
948
974
|
borderRadius: 4,
|
|
949
975
|
backgroundColor: macOSColors.background.hover,
|
|
950
|
-
justifyContent: "center"
|
|
976
|
+
justifyContent: "center",
|
|
977
|
+
overflow: "hidden"
|
|
951
978
|
},
|
|
952
979
|
phaseBar: {
|
|
953
980
|
height: 7,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { memo } from "react";
|
|
4
4
|
import { StyleSheet, View, Text } from "react-native";
|
|
5
|
-
import { ChevronRight, Upload, Download, Clock, AlertCircle, Bookmark, Pin, DevToolsCard, MethodBadge, TypeBadge, macOSColors } from "@buoy-gg/shared-ui";
|
|
5
|
+
import { ChevronRight, Upload, Download, Clock, AlertCircle, Bookmark, FlaskConical, Pin, DevToolsCard, MethodBadge, TypeBadge, macOSColors } from "@buoy-gg/shared-ui";
|
|
6
6
|
import { formatBytes, formatDuration } from "../utils/formatting";
|
|
7
7
|
import { formatRelativeTime } from "@buoy-gg/shared-ui";
|
|
8
8
|
import { useTickEveryMinute } from "../hooks/useTickEveryMinute";
|
|
@@ -170,6 +170,7 @@ function NetworkEventItemCompact({
|
|
|
170
170
|
onPress,
|
|
171
171
|
pinned = false,
|
|
172
172
|
saved = false,
|
|
173
|
+
overridden = false,
|
|
173
174
|
onLongPress
|
|
174
175
|
}) {
|
|
175
176
|
const statusColor = getStatusColor(event.status, event.error);
|
|
@@ -208,6 +209,9 @@ function NetworkEventItemCompact({
|
|
|
208
209
|
}) : null, saved ? /*#__PURE__*/_jsx(Bookmark, {
|
|
209
210
|
size: 10,
|
|
210
211
|
color: macOSColors.semantic.debug
|
|
212
|
+
}) : null, overridden || event.override ? /*#__PURE__*/_jsx(FlaskConical, {
|
|
213
|
+
size: 10,
|
|
214
|
+
color: macOSColors.semantic.warning
|
|
211
215
|
}) : null, /*#__PURE__*/_jsx(StatusIndicator, {
|
|
212
216
|
event: event,
|
|
213
217
|
isPending: isPending,
|
|
@@ -214,7 +214,8 @@ export function NetworkHeaderCopyButton({
|
|
|
214
214
|
ignoredPatterns,
|
|
215
215
|
copySettings,
|
|
216
216
|
paused,
|
|
217
|
-
onUpgradePress
|
|
217
|
+
onUpgradePress,
|
|
218
|
+
label
|
|
218
219
|
}) {
|
|
219
220
|
const hasEvents = useNetworkHasMatches({
|
|
220
221
|
filter,
|
|
@@ -222,15 +223,18 @@ export function NetworkHeaderCopyButton({
|
|
|
222
223
|
paused
|
|
223
224
|
});
|
|
224
225
|
if (!isPro) {
|
|
225
|
-
return /*#__PURE__*/
|
|
226
|
+
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
226
227
|
"sentry-label": "ignore copy pro",
|
|
227
228
|
onPress: onUpgradePress,
|
|
228
|
-
style: [styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
|
|
229
|
+
style: [label ? styles.copyMenuRow : styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
|
|
229
230
|
disabled: !hasEvents,
|
|
230
|
-
children: /*#__PURE__*/_jsx(Copy, {
|
|
231
|
+
children: [/*#__PURE__*/_jsx(Copy, {
|
|
231
232
|
size: 14,
|
|
232
233
|
color: hasEvents ? macOSColors.text.secondary : macOSColors.text.disabled
|
|
233
|
-
})
|
|
234
|
+
}), label ? /*#__PURE__*/_jsx(Text, {
|
|
235
|
+
style: styles.copyMenuLabel,
|
|
236
|
+
children: label
|
|
237
|
+
}) : null]
|
|
234
238
|
});
|
|
235
239
|
}
|
|
236
240
|
return /*#__PURE__*/_jsx(CopyButton
|
|
@@ -239,7 +243,9 @@ export function NetworkHeaderCopyButton({
|
|
|
239
243
|
, {
|
|
240
244
|
value: () => generateNetworkCopyText(filterNetworkEvents(applyIgnoredPatterns(networkEventStore.getEvents(), ignoredPatterns), filter), copySettings),
|
|
241
245
|
size: 14,
|
|
242
|
-
|
|
246
|
+
label: label,
|
|
247
|
+
buttonStyle: label ? styles.copyMenuRow : styles.headerActionButton,
|
|
248
|
+
labelStyle: styles.copyMenuLabel,
|
|
243
249
|
colors: {
|
|
244
250
|
idle: hasEvents ? macOSColors.text.secondary : macOSColors.text.disabled,
|
|
245
251
|
success: macOSColors.semantic.success,
|
|
@@ -249,6 +255,20 @@ export function NetworkHeaderCopyButton({
|
|
|
249
255
|
});
|
|
250
256
|
}
|
|
251
257
|
const styles = StyleSheet.create({
|
|
258
|
+
/** The overflow-menu variant: a full-width row, matching the other items. */
|
|
259
|
+
copyMenuRow: {
|
|
260
|
+
flexDirection: "row",
|
|
261
|
+
alignItems: "center",
|
|
262
|
+
gap: 10,
|
|
263
|
+
paddingHorizontal: 6,
|
|
264
|
+
paddingVertical: 11,
|
|
265
|
+
justifyContent: "flex-start"
|
|
266
|
+
},
|
|
267
|
+
copyMenuLabel: {
|
|
268
|
+
fontSize: 13,
|
|
269
|
+
fontWeight: "600",
|
|
270
|
+
color: macOSColors.text.primary
|
|
271
|
+
},
|
|
252
272
|
headerChipRow: {
|
|
253
273
|
flexDirection: "row",
|
|
254
274
|
alignItems: "center",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
4
|
+
import { absoluteFill, macOSColors } from "@buoy-gg/shared-ui";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The header's overflow menu.
|
|
8
|
+
*
|
|
9
|
+
* The toolbar had grown to three status badges and six icon buttons, which on a
|
|
10
|
+
* 402pt screen meant the pending badge was being overlapped by the search
|
|
11
|
+
* button. Something had to leave, and the rule for deciding what is:
|
|
12
|
+
*
|
|
13
|
+
* **A control that COMMUNICATES STATE stays visible. A destination or a rare
|
|
14
|
+
* action goes in the menu.**
|
|
15
|
+
*
|
|
16
|
+
* That keeps the power toggle (are we capturing?), the overrides flask (is the
|
|
17
|
+
* app being lied to, and how many rules?), the status badges (what happened)
|
|
18
|
+
* and Clear (the reproduce loop is clear → repro → read, and burying the first
|
|
19
|
+
* step doubles it). Search stays because it's the only way through 500 rows.
|
|
20
|
+
*
|
|
21
|
+
* Filters, Saved and Copy leave. Filters is the interesting one — it does show
|
|
22
|
+
* state, so the ⋯ carries a dot when a filter is applied rather than hiding
|
|
23
|
+
* that fact.
|
|
24
|
+
*/
|
|
25
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
26
|
+
export function NetworkHeaderMenuButton({
|
|
27
|
+
onPress,
|
|
28
|
+
isOpen,
|
|
29
|
+
hasIndicator
|
|
30
|
+
}) {
|
|
31
|
+
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
32
|
+
onPress: onPress,
|
|
33
|
+
"sentry-label": "ignore toggle header menu",
|
|
34
|
+
accessibilityLabel: isOpen ? "Close menu" : "More actions",
|
|
35
|
+
style: [styles.button, isOpen && styles.buttonOpen],
|
|
36
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
37
|
+
style: styles.dots,
|
|
38
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
39
|
+
style: [styles.dot, isOpen && styles.dotOpen]
|
|
40
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
41
|
+
style: [styles.dot, isOpen && styles.dotOpen]
|
|
42
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
43
|
+
style: [styles.dot, isOpen && styles.dotOpen]
|
|
44
|
+
})]
|
|
45
|
+
}), hasIndicator ? /*#__PURE__*/_jsx(View, {
|
|
46
|
+
style: styles.indicator
|
|
47
|
+
}) : null]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The dropdown itself, rendered in the modal BODY rather than the header —
|
|
52
|
+
* the header is a fixed-height row and anything overflowing it is clipped, so
|
|
53
|
+
* a panel anchored to the top-right of the body lands exactly where a dropdown
|
|
54
|
+
* from the ⋯ should appear.
|
|
55
|
+
*/
|
|
56
|
+
export function NetworkHeaderMenu({
|
|
57
|
+
onClose,
|
|
58
|
+
children
|
|
59
|
+
}) {
|
|
60
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
61
|
+
style: styles.overlay,
|
|
62
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
63
|
+
style: styles.backdrop,
|
|
64
|
+
activeOpacity: 1,
|
|
65
|
+
onPress: onClose,
|
|
66
|
+
"sentry-label": "ignore dismiss header menu"
|
|
67
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
68
|
+
style: styles.panel,
|
|
69
|
+
children: children
|
|
70
|
+
})]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function NetworkHeaderMenuItem({
|
|
74
|
+
icon,
|
|
75
|
+
label,
|
|
76
|
+
detail,
|
|
77
|
+
onPress
|
|
78
|
+
}) {
|
|
79
|
+
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
80
|
+
onPress: onPress,
|
|
81
|
+
"sentry-label": "ignore header menu item",
|
|
82
|
+
style: styles.item,
|
|
83
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
84
|
+
style: styles.itemIcon,
|
|
85
|
+
children: icon
|
|
86
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
87
|
+
style: styles.itemLabel,
|
|
88
|
+
children: label
|
|
89
|
+
}), detail ? /*#__PURE__*/_jsx(Text, {
|
|
90
|
+
style: styles.itemDetail,
|
|
91
|
+
children: detail
|
|
92
|
+
}) : null]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const styles = StyleSheet.create({
|
|
96
|
+
// The same 32×32 box every other header action uses, minus the fill and
|
|
97
|
+
// border. Padding alone gave a 3pt-wide hit area AND a shorter box than its
|
|
98
|
+
// neighbours, which is why the dots sat high in the row; a fixed square
|
|
99
|
+
// centres the glyph on the same baseline as the search/power/clear icons.
|
|
100
|
+
button: {
|
|
101
|
+
width: 32,
|
|
102
|
+
height: 32,
|
|
103
|
+
borderRadius: 8,
|
|
104
|
+
alignItems: "center",
|
|
105
|
+
justifyContent: "center"
|
|
106
|
+
},
|
|
107
|
+
// Only lit while the menu is down — the button is the one thing not covered
|
|
108
|
+
// by the backdrop, so it has to look like the thing that is currently open.
|
|
109
|
+
buttonOpen: {
|
|
110
|
+
backgroundColor: macOSColors.background.hover
|
|
111
|
+
},
|
|
112
|
+
dots: {
|
|
113
|
+
flexDirection: "column",
|
|
114
|
+
gap: 2.5,
|
|
115
|
+
alignItems: "center"
|
|
116
|
+
},
|
|
117
|
+
dot: {
|
|
118
|
+
width: 3,
|
|
119
|
+
height: 3,
|
|
120
|
+
borderRadius: 2,
|
|
121
|
+
backgroundColor: macOSColors.text.secondary
|
|
122
|
+
},
|
|
123
|
+
dotOpen: {
|
|
124
|
+
backgroundColor: macOSColors.text.primary
|
|
125
|
+
},
|
|
126
|
+
indicator: {
|
|
127
|
+
position: "absolute",
|
|
128
|
+
// Sits just up-and-right of the top dot. Anchored to the 32pt box's corner
|
|
129
|
+
// it would float off on its own, since the dot column is only 3pt wide.
|
|
130
|
+
top: 6,
|
|
131
|
+
right: 8,
|
|
132
|
+
width: 6,
|
|
133
|
+
height: 6,
|
|
134
|
+
borderRadius: 3,
|
|
135
|
+
backgroundColor: macOSColors.semantic.info
|
|
136
|
+
},
|
|
137
|
+
overlay: {
|
|
138
|
+
...absoluteFill,
|
|
139
|
+
zIndex: 50
|
|
140
|
+
},
|
|
141
|
+
backdrop: {
|
|
142
|
+
...absoluteFill,
|
|
143
|
+
backgroundColor: "rgba(0,0,0,0.5)"
|
|
144
|
+
},
|
|
145
|
+
panel: {
|
|
146
|
+
position: "absolute",
|
|
147
|
+
top: 6,
|
|
148
|
+
right: 8,
|
|
149
|
+
minWidth: 196,
|
|
150
|
+
paddingVertical: 4,
|
|
151
|
+
borderRadius: 8,
|
|
152
|
+
borderWidth: 1,
|
|
153
|
+
borderColor: macOSColors.border.hover,
|
|
154
|
+
backgroundColor: macOSColors.background.card
|
|
155
|
+
},
|
|
156
|
+
item: {
|
|
157
|
+
flexDirection: "row",
|
|
158
|
+
alignItems: "center",
|
|
159
|
+
gap: 10,
|
|
160
|
+
paddingHorizontal: 12,
|
|
161
|
+
paddingVertical: 11
|
|
162
|
+
},
|
|
163
|
+
itemIcon: {
|
|
164
|
+
width: 16,
|
|
165
|
+
alignItems: "center"
|
|
166
|
+
},
|
|
167
|
+
itemLabel: {
|
|
168
|
+
flex: 1,
|
|
169
|
+
fontSize: 13,
|
|
170
|
+
fontWeight: "600",
|
|
171
|
+
color: macOSColors.text.primary
|
|
172
|
+
},
|
|
173
|
+
itemDetail: {
|
|
174
|
+
fontSize: 11,
|
|
175
|
+
color: macOSColors.text.muted
|
|
176
|
+
}
|
|
177
|
+
});
|