@buoy-gg/network 7.0.25 → 7.0.27
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/network/components/NetworkDetailStepper.js +2 -1
- package/lib/commonjs/network/components/NetworkEventDetailView.js +74 -72
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +24 -20
- package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +24 -6
- package/lib/commonjs/network/components/NetworkFilterViewV3.js +12 -12
- package/lib/commonjs/network/components/NetworkHeaderLive.js +25 -29
- package/lib/commonjs/network/components/NetworkHeaderLive.web.js +24 -6
- package/lib/commonjs/network/components/NetworkHeaderMenu.js +10 -9
- package/lib/commonjs/network/components/NetworkListScreen.js +2 -1
- package/lib/commonjs/network/components/NetworkListScreen.web.js +24 -6
- package/lib/commonjs/network/components/NetworkModal.js +45 -31
- package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +10 -7
- package/lib/commonjs/network/components/NetworkOverridesButton.js +7 -7
- package/lib/commonjs/network/sync/networkSyncAdapter.js +36 -16
- package/lib/commonjs/network/utils/networkEventStore.js +26 -0
- package/lib/commonjs/network/utils/networkListener.js +173 -39
- package/lib/module/network/components/NetworkDetailStepper.js +2 -1
- package/lib/module/network/components/NetworkEventDetailView.js +75 -73
- package/lib/module/network/components/NetworkEventItemCompact.js +25 -21
- package/lib/module/network/components/NetworkEventItemCompact.web.js +24 -6
- package/lib/module/network/components/NetworkFilterViewV3.js +13 -13
- package/lib/module/network/components/NetworkHeaderLive.js +26 -30
- package/lib/module/network/components/NetworkHeaderLive.web.js +24 -6
- package/lib/module/network/components/NetworkHeaderMenu.js +11 -10
- package/lib/module/network/components/NetworkListScreen.js +3 -2
- package/lib/module/network/components/NetworkListScreen.web.js +24 -6
- package/lib/module/network/components/NetworkModal.js +46 -32
- package/lib/module/network/components/NetworkOverrideHeaderButton.js +11 -8
- package/lib/module/network/components/NetworkOverridesButton.js +8 -8
- package/lib/module/network/sync/networkSyncAdapter.js +37 -17
- package/lib/module/network/utils/networkEventStore.js +26 -0
- package/lib/module/network/utils/networkListener.js +171 -38
- package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts +1 -0
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkEventStore.d.ts +17 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkListener.d.ts +61 -5
- package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -79,24 +79,27 @@ function NetworkOverrideHeaderButton({
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
const styles = _reactNative.StyleSheet.create({
|
|
82
|
+
// Night: the header's dark-glass chip, with the accent label the night
|
|
83
|
+
// language gives labelled actions (plate + accent ink). Active keeps the
|
|
84
|
+
// warning tint — same "you are not seeing the truth" signal as the flask.
|
|
82
85
|
button: {
|
|
83
86
|
paddingHorizontal: 10,
|
|
84
87
|
paddingVertical: 6,
|
|
85
|
-
borderRadius:
|
|
88
|
+
borderRadius: 8,
|
|
86
89
|
borderWidth: 1,
|
|
87
|
-
borderColor: _sharedUi.
|
|
88
|
-
backgroundColor: _sharedUi.
|
|
90
|
+
borderColor: _sharedUi.night.color.border,
|
|
91
|
+
backgroundColor: _sharedUi.night.color.buttonSurface
|
|
89
92
|
},
|
|
90
93
|
buttonActive: {
|
|
91
|
-
borderColor: _sharedUi.
|
|
92
|
-
backgroundColor: _sharedUi.
|
|
94
|
+
borderColor: _sharedUi.night.color.warning + "77",
|
|
95
|
+
backgroundColor: _sharedUi.night.color.warningSoft
|
|
93
96
|
},
|
|
94
97
|
label: {
|
|
95
98
|
fontSize: 11,
|
|
96
99
|
fontWeight: "700",
|
|
97
|
-
color: _sharedUi.
|
|
100
|
+
color: _sharedUi.night.color.accent
|
|
98
101
|
},
|
|
99
102
|
labelActive: {
|
|
100
|
-
color: _sharedUi.
|
|
103
|
+
color: _sharedUi.night.color.warning
|
|
101
104
|
}
|
|
102
105
|
});
|
|
@@ -35,7 +35,7 @@ function NetworkOverridesButton({
|
|
|
35
35
|
accessibilityLabel: active ? `Response overrides — ${activeCount} active` : "Response overrides",
|
|
36
36
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FlaskConical, {
|
|
37
37
|
size: 14,
|
|
38
|
-
color: active ? _sharedUi.
|
|
38
|
+
color: active ? _sharedUi.night.color.warning : _sharedUi.night.color.textSecondary
|
|
39
39
|
}), active ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
40
40
|
style: styles.badge,
|
|
41
41
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -56,15 +56,15 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
56
56
|
borderRadius: 8,
|
|
57
57
|
alignItems: "center",
|
|
58
58
|
justifyContent: "center",
|
|
59
|
-
backgroundColor: _sharedUi.
|
|
59
|
+
backgroundColor: _sharedUi.night.color.buttonSurface,
|
|
60
60
|
borderWidth: 1,
|
|
61
|
-
borderColor: _sharedUi.
|
|
61
|
+
borderColor: _sharedUi.night.color.border
|
|
62
62
|
},
|
|
63
63
|
// Active fills the whole box, the way the power toggle goes green — the
|
|
64
64
|
// warning now reads at a glance instead of as a tinted glyph.
|
|
65
65
|
buttonActive: {
|
|
66
|
-
backgroundColor: _sharedUi.
|
|
67
|
-
borderColor: _sharedUi.
|
|
66
|
+
backgroundColor: _sharedUi.night.color.warningSoft,
|
|
67
|
+
borderColor: _sharedUi.night.color.warning + "77"
|
|
68
68
|
},
|
|
69
69
|
badge: {
|
|
70
70
|
position: "absolute",
|
|
@@ -78,11 +78,11 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
78
78
|
paddingHorizontal: 3,
|
|
79
79
|
alignItems: "center",
|
|
80
80
|
justifyContent: "center",
|
|
81
|
-
backgroundColor: _sharedUi.
|
|
81
|
+
backgroundColor: _sharedUi.night.color.warning
|
|
82
82
|
},
|
|
83
83
|
badgeText: {
|
|
84
84
|
fontSize: 8,
|
|
85
85
|
fontWeight: "800",
|
|
86
|
-
color:
|
|
86
|
+
color: "#000000"
|
|
87
87
|
}
|
|
88
88
|
});
|
|
@@ -364,22 +364,30 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
|
|
|
364
364
|
// flags, and a total snapshot budget so the list degrades instead of the
|
|
365
365
|
// whole panel being dropped at the emit layer.
|
|
366
366
|
version: 5,
|
|
367
|
-
getSnapshot: () =>
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
367
|
+
getSnapshot: () => {
|
|
368
|
+
// Piggyback the interception health check on the watch loop: while a
|
|
369
|
+
// dashboard/MCP watch is open this runs continuously, so a clobbered
|
|
370
|
+
// fetch/XHR patch (see networkListener.startListening) heals within one
|
|
371
|
+
// snapshot tick instead of staying dead for the session. Two property
|
|
372
|
+
// reads when healthy.
|
|
373
|
+
_networkEventStore.networkEventStore.ensureInterception();
|
|
374
|
+
return {
|
|
375
|
+
events: fitEventsToBudget(_networkEventStore.networkEventStore.getEvents()),
|
|
376
|
+
/**
|
|
377
|
+
* Response-override rules live on the DEVICE — that's the only place they
|
|
378
|
+
* can run, since a rule is applied inside the app's own XMLHttpRequest.
|
|
379
|
+
* Remote surfaces mirror this and forward their edits back as actions.
|
|
380
|
+
*/
|
|
381
|
+
overrides: stripLargeRuleBodies(_overrideRulesStore.overrideRulesStore.getState()),
|
|
382
|
+
// Saved snapshots go through the same body strip. They are few (capped in
|
|
383
|
+
// the store) but they ride on EVERY snapshot, so a fat one would land in
|
|
384
|
+
// the same 200ms loop that the body-stripping exists to protect.
|
|
385
|
+
saved: _networkSavedStore.networkSavedStore.getRecords().map(record => ({
|
|
386
|
+
...record,
|
|
387
|
+
event: toWireEvent(record.event).event
|
|
388
|
+
}))
|
|
389
|
+
};
|
|
390
|
+
},
|
|
383
391
|
subscribe: onChange => {
|
|
384
392
|
// Pinning with no traffic in flight still has to reach the dashboard, so
|
|
385
393
|
// both stores drive the snapshot.
|
|
@@ -590,10 +598,22 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
|
|
|
590
598
|
} catch {
|
|
591
599
|
/* no interceptable runtime here */
|
|
592
600
|
}
|
|
601
|
+
// `interceptorInstalled` is the listener's own flag; `interceptorLive`
|
|
602
|
+
// is whether the interceptors are actually reachable through the global
|
|
603
|
+
// chain. installed:true + live:false = something re-assigned fetch/XHR
|
|
604
|
+
// over the interceptor (it self-heals on the next subscribe/snapshot —
|
|
605
|
+
// see ensureInterception).
|
|
606
|
+
let interceptorLive = null;
|
|
607
|
+
try {
|
|
608
|
+
interceptorLive = (0, _networkListener.isNetworkInterceptionLive)();
|
|
609
|
+
} catch {
|
|
610
|
+
/* no interceptable runtime here */
|
|
611
|
+
}
|
|
593
612
|
return {
|
|
594
613
|
capturing: subscribers > 0,
|
|
595
614
|
subscribers,
|
|
596
615
|
interceptorInstalled,
|
|
616
|
+
interceptorLive,
|
|
597
617
|
listenerCount,
|
|
598
618
|
eventCount: _networkEventStore.networkEventStore.getEvents().length
|
|
599
619
|
};
|
|
@@ -175,6 +175,32 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
|
|
|
175
175
|
return this.rawListenerUnsubscribe !== null;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Re-assert the interceptor if something re-assigned fetch/XHR since capture
|
|
180
|
+
* started (Expo's runtime, RN DevTools, a second dev server's tooling — see
|
|
181
|
+
* networkListener.startListening). A no-op costing two property reads when
|
|
182
|
+
* everything is healthy, and a no-op entirely unless this store is actively
|
|
183
|
+
* capturing — a mirror-mode dashboard must never install an interceptor in
|
|
184
|
+
* its own process.
|
|
185
|
+
*/
|
|
186
|
+
ensureInterception() {
|
|
187
|
+
if (!this.isCapturing()) return;
|
|
188
|
+
(0, _networkListener.networkListener)().startListening();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Every new consumer — the Network modal opening, a badge mounting, a
|
|
193
|
+
* dashboard watch attaching — is a chance to notice and repair a clobbered
|
|
194
|
+
* interceptor, which matters because a long-lived watch can hold the
|
|
195
|
+
* subscriber count above zero for the whole session (so the 0→1
|
|
196
|
+
* startCapturing path would never run again).
|
|
197
|
+
*/
|
|
198
|
+
subscribeToEvents(listener) {
|
|
199
|
+
const unsubscribe = super.subscribeToEvents(listener);
|
|
200
|
+
this.ensureInterception();
|
|
201
|
+
return unsubscribe;
|
|
202
|
+
}
|
|
203
|
+
|
|
178
204
|
/**
|
|
179
205
|
* Process a network listener event
|
|
180
206
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.stopNetworkListener = exports.startNetworkListener = exports.removeAllNetworkListeners = exports.networkListener = exports.isNetworkListening = exports.getNetworkListenerCount = exports.addNetworkListener = void 0;
|
|
6
|
+
exports.stopNetworkListener = exports.startNetworkListener = exports.removeAllNetworkListeners = exports.networkListener = exports.isNetworkListening = exports.isNetworkInterceptionLive = exports.getNetworkListenerCount = exports.addNetworkListener = exports.BUOY_XHR_INTERCEPTOR_FLAG = exports.BUOY_FETCH_INTERCEPTOR_FLAG = void 0;
|
|
7
7
|
var _engine = require("../overrides/engine");
|
|
8
8
|
var _applyToXhr = require("../overrides/applyToXhr");
|
|
9
9
|
var _applyToFetch = require("../overrides/applyToFetch");
|
|
@@ -31,6 +31,18 @@ let insideOverriddenFetch = false;
|
|
|
31
31
|
|
|
32
32
|
// Event types for network operations
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Markers stamped on the installed interceptor functions so their presence in
|
|
36
|
+
* the live chain stays detectable even after another tool wraps them.
|
|
37
|
+
* Cooperating patchers (@buoy-gg/impersonate) copy every `__buoy*` marker from
|
|
38
|
+
* the function they wrap onto their own wrapper; a global fetch/XHR WITHOUT
|
|
39
|
+
* the marker therefore means our interceptor was clobbered — some runtime
|
|
40
|
+
* re-assigned `globalThis.fetch` over it (Expo's winter runtime, React Native
|
|
41
|
+
* DevTools' network panel, a second dev server's tooling, hot-reload churn all
|
|
42
|
+
* do this in the wild) — and capture must re-install. See startListening.
|
|
43
|
+
*/
|
|
44
|
+
const BUOY_FETCH_INTERCEPTOR_FLAG = exports.BUOY_FETCH_INTERCEPTOR_FLAG = "__buoyNetworkFetchIntercepted";
|
|
45
|
+
const BUOY_XHR_INTERCEPTOR_FLAG = exports.BUOY_XHR_INTERCEPTOR_FLAG = "__buoyNetworkXhrIntercepted";
|
|
34
46
|
/**
|
|
35
47
|
* Network traffic interceptor for React Native applications
|
|
36
48
|
*
|
|
@@ -71,6 +83,25 @@ class NetworkListener {
|
|
|
71
83
|
|
|
72
84
|
// Store original methods
|
|
73
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The wrappers currently installed by THIS instance, for identity checks:
|
|
88
|
+
* `stopListening` restores a global only when it is still literally ours —
|
|
89
|
+
* assigning our saved original over someone who patched after us would wipe
|
|
90
|
+
* their interceptor exactly the way this class gets wiped (see below).
|
|
91
|
+
*/
|
|
92
|
+
patchedFetch = null;
|
|
93
|
+
patchedXHROpen = null;
|
|
94
|
+
patchedXHRSend = null;
|
|
95
|
+
patchedXHRSetRequestHeader = null;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Bumped on every (re)install and on stop. Each wrapper closes over the
|
|
99
|
+
* generation it was born with and goes silent (pure pass-through — no emit,
|
|
100
|
+
* no counter bump) once a newer generation exists, so a wrapper stranded
|
|
101
|
+
* inside another tool's chain can never double-report a request.
|
|
102
|
+
*/
|
|
103
|
+
fetchGeneration = 0;
|
|
104
|
+
xhrGeneration = 0;
|
|
74
105
|
constructor() {
|
|
75
106
|
// Store original methods
|
|
76
107
|
this.originalFetch = globalThis.fetch.bind(globalThis);
|
|
@@ -408,30 +439,82 @@ class NetworkListener {
|
|
|
408
439
|
}
|
|
409
440
|
}
|
|
410
441
|
|
|
442
|
+
/** Is our fetch interceptor still reachable through the global chain? */
|
|
443
|
+
isFetchInterceptionLive() {
|
|
444
|
+
return globalThis.fetch[BUOY_FETCH_INTERCEPTOR_FLAG] === true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Is our XHR interceptor still reachable through the prototype chain? */
|
|
448
|
+
isXhrInterceptionLive() {
|
|
449
|
+
return XMLHttpRequest.prototype.send[BUOY_XHR_INTERCEPTOR_FLAG] === true;
|
|
450
|
+
}
|
|
451
|
+
|
|
411
452
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
|
|
453
|
+
* Both halves of the interceptor are reachable — requests actually flow
|
|
454
|
+
* through capture. `isListening` alone cannot promise that (see
|
|
455
|
+
* startListening); diagnostics should report THIS.
|
|
456
|
+
*/
|
|
457
|
+
isInterceptionLive() {
|
|
458
|
+
return this.isFetchInterceptionLive() && this.isXhrInterceptionLive();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Start (or repair) interception by swizzling fetch and XMLHttpRequest.
|
|
416
463
|
*
|
|
417
|
-
*
|
|
464
|
+
* NOT guarded by a plain `isListening` flag: `globalThis.fetch` gets
|
|
465
|
+
* re-assigned in the wild — Expo's winter runtime, React Native DevTools'
|
|
466
|
+
* network panel, a second dev server's tooling, hot-reload churn. When that
|
|
467
|
+
* happens the flag still says "listening" while every request bypasses
|
|
468
|
+
* capture, and a flag-only guard makes the outage PERMANENT (observed as
|
|
469
|
+
* "No network events" forever with `interceptorInstalled: true`). So this
|
|
470
|
+
* verifies each interceptor is actually reachable (marker check) and
|
|
471
|
+
* re-installs whichever half was clobbered, wrapping the CURRENT global so
|
|
472
|
+
* whatever replaced us keeps working underneath. When both halves are live
|
|
473
|
+
* this costs two property reads.
|
|
418
474
|
*
|
|
419
475
|
* @performance Uses method swizzling for minimal runtime overhead
|
|
420
476
|
* @performance Includes request deduplication through ignored URL patterns
|
|
421
477
|
*/
|
|
422
478
|
startListening() {
|
|
423
|
-
|
|
479
|
+
const fetchLive = this.isListening && this.isFetchInterceptionLive();
|
|
480
|
+
const xhrLive = this.isListening && this.isXhrInterceptionLive();
|
|
481
|
+
if (fetchLive && xhrLive) {
|
|
424
482
|
return;
|
|
425
483
|
}
|
|
426
|
-
|
|
484
|
+
if (!fetchLive) {
|
|
485
|
+
this.installFetchInterceptor();
|
|
486
|
+
}
|
|
487
|
+
if (!xhrLive) {
|
|
488
|
+
this.installXhrInterceptor();
|
|
489
|
+
}
|
|
490
|
+
this.isListening = true;
|
|
491
|
+
if (__DEV__) {
|
|
492
|
+
// Network listener has started monitoring fetch and XMLHttpRequest operations
|
|
493
|
+
}
|
|
494
|
+
}
|
|
427
495
|
|
|
428
|
-
|
|
429
|
-
|
|
496
|
+
/** Swizzle `globalThis.fetch`, wrapping whatever is installed right now. */
|
|
497
|
+
installFetchInterceptor() {
|
|
498
|
+
const self = this;
|
|
499
|
+
// The pass-through is the CURRENT global (not the construction-time one):
|
|
500
|
+
// re-installing after a clobber must call through the replacement, and a
|
|
501
|
+
// stale generation must replay the chain exactly as it stood beneath it.
|
|
502
|
+
const passthroughFetch = globalThis.fetch.bind(globalThis);
|
|
503
|
+
this.originalFetch = passthroughFetch;
|
|
504
|
+
const generation = ++this.fetchGeneration;
|
|
505
|
+
const wrappedFetch = async (input, init) => {
|
|
430
506
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
431
507
|
|
|
508
|
+
// A newer install supersedes this wrapper. If it is still stranded in
|
|
509
|
+
// another tool's chain it must pass through silently, or one request
|
|
510
|
+
// would be reported once per stranded generation.
|
|
511
|
+
if (generation !== self.fetchGeneration) {
|
|
512
|
+
return passthroughFetch(input, init);
|
|
513
|
+
}
|
|
514
|
+
|
|
432
515
|
// Skip ignored URLs
|
|
433
516
|
if (self.shouldIgnoreUrl(url)) {
|
|
434
|
-
return
|
|
517
|
+
return passthroughFetch(input, init);
|
|
435
518
|
}
|
|
436
519
|
const startTime = Date.now();
|
|
437
520
|
const requestId = `fetch_${++self.requestCounter}`;
|
|
@@ -514,7 +597,7 @@ class NetworkListener {
|
|
|
514
597
|
insideOverriddenFetch = outcome != null;
|
|
515
598
|
let pending;
|
|
516
599
|
try {
|
|
517
|
-
pending =
|
|
600
|
+
pending = passthroughFetch(input, init);
|
|
518
601
|
} finally {
|
|
519
602
|
insideOverriddenFetch = false;
|
|
520
603
|
}
|
|
@@ -617,30 +700,51 @@ class NetworkListener {
|
|
|
617
700
|
throw error;
|
|
618
701
|
}
|
|
619
702
|
};
|
|
703
|
+
wrappedFetch[BUOY_FETCH_INTERCEPTOR_FLAG] = true;
|
|
704
|
+
this.patchedFetch = wrappedFetch;
|
|
705
|
+
globalThis.fetch = this.patchedFetch;
|
|
706
|
+
}
|
|
620
707
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
708
|
+
/** Swizzle the XHR prototype methods, wrapping whatever is installed now. */
|
|
709
|
+
installXhrInterceptor() {
|
|
710
|
+
const self = this;
|
|
711
|
+
// Same contract as the fetch half: pass through to the CURRENT methods.
|
|
712
|
+
const passthroughOpen = XMLHttpRequest.prototype.open;
|
|
713
|
+
const passthroughSend = XMLHttpRequest.prototype.send;
|
|
714
|
+
const passthroughSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
715
|
+
this.originalXHROpen = passthroughOpen;
|
|
716
|
+
this.originalXHRSend = passthroughSend;
|
|
717
|
+
this.originalXHRSetRequestHeader = passthroughSetRequestHeader;
|
|
718
|
+
const generation = ++this.xhrGeneration;
|
|
719
|
+
const patchedOpen = function (method, url, async, user, password) {
|
|
720
|
+
// Superseded generations must not re-stamp (a double stamp burns two
|
|
721
|
+
// request ids per request) — just pass through.
|
|
722
|
+
if (generation === self.xhrGeneration) {
|
|
723
|
+
const xhr = this;
|
|
724
|
+
xhr._requestId = `xhr_${++self.requestCounter}`;
|
|
725
|
+
xhr._method = method;
|
|
726
|
+
xhr._url = url;
|
|
727
|
+
xhr._startTime = Date.now();
|
|
728
|
+
xhr._requestHeaders = {};
|
|
729
|
+
}
|
|
730
|
+
return passthroughOpen.call(this, method, url, async ?? true, user, password);
|
|
631
731
|
};
|
|
632
732
|
|
|
633
733
|
// Track request headers
|
|
634
|
-
|
|
734
|
+
const patchedSetRequestHeader = function (header, value) {
|
|
635
735
|
const xhr = this;
|
|
636
|
-
if (xhr._requestHeaders) {
|
|
736
|
+
if (generation === self.xhrGeneration && xhr._requestHeaders) {
|
|
637
737
|
xhr._requestHeaders[header] = value;
|
|
638
738
|
}
|
|
639
|
-
return
|
|
739
|
+
return passthroughSetRequestHeader.call(this, header, value);
|
|
640
740
|
};
|
|
641
|
-
|
|
741
|
+
const patchedSend = function (
|
|
642
742
|
// @ts-ignore - this does exist on native
|
|
643
743
|
data) {
|
|
744
|
+
// Superseded generation: silent pass-through (see the fetch wrapper).
|
|
745
|
+
if (generation !== self.xhrGeneration) {
|
|
746
|
+
return passthroughSend.call(this, data);
|
|
747
|
+
}
|
|
644
748
|
const xhr = this;
|
|
645
749
|
const requestId = xhr._requestId;
|
|
646
750
|
const method = xhr._method || "GET";
|
|
@@ -650,7 +754,7 @@ class NetworkListener {
|
|
|
650
754
|
|
|
651
755
|
// Skip ignored URLs
|
|
652
756
|
if (self.shouldIgnoreUrl(url)) {
|
|
653
|
-
return
|
|
757
|
+
return passthroughSend.call(this, data);
|
|
654
758
|
}
|
|
655
759
|
const {
|
|
656
760
|
url: cleanUrl,
|
|
@@ -767,18 +871,21 @@ class NetworkListener {
|
|
|
767
871
|
// Listeners are attached above FIRST: `runOverride` drives the same
|
|
768
872
|
// readyState transitions a real response would, and the interceptor has
|
|
769
873
|
// to see them (readyState 2 is where TTFB is stamped).
|
|
770
|
-
const handled = (0, _applyToXhr.runOverride)(this, url, outcome, () =>
|
|
874
|
+
const handled = (0, _applyToXhr.runOverride)(this, url, outcome, () => passthroughSend.call(this, data));
|
|
771
875
|
// `handled` is false only where this isn't React Native's own
|
|
772
876
|
// XMLHttpRequest and can't be driven by hand. Fall through and send for
|
|
773
877
|
// real rather than dropping the request on the floor.
|
|
774
878
|
if (handled) return;
|
|
775
879
|
}
|
|
776
|
-
return
|
|
880
|
+
return passthroughSend.call(this, data);
|
|
777
881
|
};
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
882
|
+
patchedSend[BUOY_XHR_INTERCEPTOR_FLAG] = true;
|
|
883
|
+
this.patchedXHROpen = patchedOpen;
|
|
884
|
+
this.patchedXHRSend = patchedSend;
|
|
885
|
+
this.patchedXHRSetRequestHeader = patchedSetRequestHeader;
|
|
886
|
+
XMLHttpRequest.prototype.open = this.patchedXHROpen;
|
|
887
|
+
XMLHttpRequest.prototype.send = this.patchedXHRSend;
|
|
888
|
+
XMLHttpRequest.prototype.setRequestHeader = this.patchedXHRSetRequestHeader;
|
|
782
889
|
}
|
|
783
890
|
|
|
784
891
|
/**
|
|
@@ -792,11 +899,30 @@ class NetworkListener {
|
|
|
792
899
|
return;
|
|
793
900
|
}
|
|
794
901
|
|
|
795
|
-
//
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
902
|
+
// Silence any of our wrappers stranded inside another tool's chain — they
|
|
903
|
+
// check the generation on every call and now pass through without emitting.
|
|
904
|
+
this.fetchGeneration++;
|
|
905
|
+
this.xhrGeneration++;
|
|
906
|
+
|
|
907
|
+
// Restore ONLY what is still literally ours. If another tool patched after
|
|
908
|
+
// us (impersonate, a debugger), assigning our saved originals would wipe
|
|
909
|
+
// ITS interceptor — the exact injury startListening defends against.
|
|
910
|
+
if (this.patchedFetch && globalThis.fetch === this.patchedFetch) {
|
|
911
|
+
globalThis.fetch = this.originalFetch;
|
|
912
|
+
}
|
|
913
|
+
if (this.patchedXHROpen && XMLHttpRequest.prototype.open === this.patchedXHROpen) {
|
|
914
|
+
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
915
|
+
}
|
|
916
|
+
if (this.patchedXHRSend && XMLHttpRequest.prototype.send === this.patchedXHRSend) {
|
|
917
|
+
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
918
|
+
}
|
|
919
|
+
if (this.patchedXHRSetRequestHeader && XMLHttpRequest.prototype.setRequestHeader === this.patchedXHRSetRequestHeader) {
|
|
920
|
+
XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
|
|
921
|
+
}
|
|
922
|
+
this.patchedFetch = null;
|
|
923
|
+
this.patchedXHROpen = null;
|
|
924
|
+
this.patchedXHRSend = null;
|
|
925
|
+
this.patchedXHRSetRequestHeader = null;
|
|
800
926
|
this.isListening = false;
|
|
801
927
|
if (__DEV__) {
|
|
802
928
|
// Network listener has stopped monitoring and restored original methods
|
|
@@ -921,4 +1047,12 @@ const isNetworkListening = () => getNetworkListener().isActive;
|
|
|
921
1047
|
*/
|
|
922
1048
|
exports.isNetworkListening = isNetworkListening;
|
|
923
1049
|
const getNetworkListenerCount = () => getNetworkListener().listenerCount;
|
|
924
|
-
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Whether both interceptors are actually reachable through the live global
|
|
1053
|
+
* chain — the honest version of `isNetworkListening` (whose flag stays true
|
|
1054
|
+
* even after another tool re-assigns `globalThis.fetch` over the wrapper).
|
|
1055
|
+
*/
|
|
1056
|
+
exports.getNetworkListenerCount = getNetworkListenerCount;
|
|
1057
|
+
const isNetworkInterceptionLive = () => getNetworkListener().isInterceptionLive();
|
|
1058
|
+
exports.isNetworkInterceptionLive = isNetworkInterceptionLive;
|
|
@@ -98,6 +98,7 @@ export const NetworkDetailStepper = /*#__PURE__*/memo(function NetworkDetailStep
|
|
|
98
98
|
itemLabel: "Request",
|
|
99
99
|
applySafeAreaInset: applySafeAreaInset,
|
|
100
100
|
onPrevious: stepPrevious,
|
|
101
|
-
onNext: stepNext
|
|
101
|
+
onNext: stepNext,
|
|
102
|
+
variant: "night"
|
|
102
103
|
});
|
|
103
104
|
});
|