@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
4
4
|
import { View, StyleSheet, TouchableOpacity, TextInput } from "react-native";
|
|
5
|
-
import { Search, Filter, Bookmark, X, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, persistentStorage,
|
|
5
|
+
import { Search, Filter, Bookmark, X, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, night, persistentStorage, NightSegmented, useFeatureGate, UpgradeModal, PowerToggleButton, useIgnoredPatterns } from "@buoy-gg/shared-ui";
|
|
6
6
|
import { NetworkFilterViewV3 } from "./NetworkFilterViewV3";
|
|
7
7
|
import { TickProvider } from "../hooks/useTickEveryMinute";
|
|
8
8
|
import { NetworkEventDetailView } from "./NetworkEventDetailView";
|
|
@@ -360,25 +360,26 @@ function NetworkModalInner({
|
|
|
360
360
|
|
|
361
361
|
// Compact header with actions (like Sentry/Storage modals)
|
|
362
362
|
const renderHeaderContent = () => {
|
|
363
|
-
// Filter view header with tabs
|
|
363
|
+
// Filter view header with tabs — the night segmented control (the same
|
|
364
|
+
// one the dial settings header uses).
|
|
364
365
|
if (showFilterView) {
|
|
365
|
-
const tabs = [{
|
|
366
|
-
key: "filters",
|
|
367
|
-
label: "Filters"
|
|
368
|
-
}, {
|
|
369
|
-
key: "copy",
|
|
370
|
-
label: "Copy"
|
|
371
|
-
}];
|
|
372
366
|
return /*#__PURE__*/_jsxs(ModalHeader, {
|
|
373
367
|
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
374
368
|
onBack: () => setShowFilterView(false)
|
|
375
369
|
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
376
370
|
title: "",
|
|
377
371
|
noMargin: true,
|
|
378
|
-
children: /*#__PURE__*/_jsx(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
372
|
+
children: /*#__PURE__*/_jsx(NightSegmented, {
|
|
373
|
+
size: "sm",
|
|
374
|
+
tabs: [{
|
|
375
|
+
key: "filters",
|
|
376
|
+
label: "Filters"
|
|
377
|
+
}, {
|
|
378
|
+
key: "copy",
|
|
379
|
+
label: "Copy"
|
|
380
|
+
}],
|
|
381
|
+
activeKey: activeTab,
|
|
382
|
+
onChange: tab => setActiveTab(tab)
|
|
382
383
|
})
|
|
383
384
|
})]
|
|
384
385
|
});
|
|
@@ -444,12 +445,12 @@ function NetworkModalInner({
|
|
|
444
445
|
style: styles.headerSearchContainer,
|
|
445
446
|
children: [/*#__PURE__*/_jsx(Search, {
|
|
446
447
|
size: 14,
|
|
447
|
-
color:
|
|
448
|
+
color: night.color.textSecondary
|
|
448
449
|
}), /*#__PURE__*/_jsx(TextInput, {
|
|
449
450
|
ref: searchInputRef,
|
|
450
451
|
style: styles.headerSearchInput,
|
|
451
452
|
placeholder: "Search URL, method, operation, error...",
|
|
452
|
-
placeholderTextColor:
|
|
453
|
+
placeholderTextColor: night.color.placeholder,
|
|
453
454
|
value: searchText,
|
|
454
455
|
onChangeText: handleSearch,
|
|
455
456
|
onSubmitEditing: () => setIsSearchActive(false),
|
|
@@ -468,7 +469,7 @@ function NetworkModalInner({
|
|
|
468
469
|
style: styles.headerSearchClear,
|
|
469
470
|
children: /*#__PURE__*/_jsx(X, {
|
|
470
471
|
size: 14,
|
|
471
|
-
color:
|
|
472
|
+
color: night.color.textSecondary
|
|
472
473
|
})
|
|
473
474
|
}) : null]
|
|
474
475
|
}) : /*#__PURE__*/_jsx(NetworkHeaderBadges, {
|
|
@@ -484,7 +485,7 @@ function NetworkModalInner({
|
|
|
484
485
|
style: styles.headerActionButton,
|
|
485
486
|
children: /*#__PURE__*/_jsx(Search, {
|
|
486
487
|
size: 14,
|
|
487
|
-
color:
|
|
488
|
+
color: night.color.textSecondary
|
|
488
489
|
})
|
|
489
490
|
}), overridesAvailable ? /*#__PURE__*/_jsx(NetworkOverridesButton, {
|
|
490
491
|
onPress: () => setShowOverridesView(true)
|
|
@@ -520,7 +521,7 @@ function NetworkModalInner({
|
|
|
520
521
|
const body = /*#__PURE__*/_jsxs(_Fragment, {
|
|
521
522
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
522
523
|
style: styles.container,
|
|
523
|
-
children: [/*#__PURE__*/_jsx(
|
|
524
|
+
children: [/*#__PURE__*/_jsx(NetworkDetailScreen, {
|
|
524
525
|
visible: hasSelection,
|
|
525
526
|
paused: paused,
|
|
526
527
|
ignoredPatternValues: ignoredPatternValues,
|
|
@@ -590,7 +591,7 @@ function NetworkModalInner({
|
|
|
590
591
|
children: [/*#__PURE__*/_jsx(NetworkHeaderMenuItem, {
|
|
591
592
|
icon: /*#__PURE__*/_jsx(Filter, {
|
|
592
593
|
size: 14,
|
|
593
|
-
color: hasActiveFilter ?
|
|
594
|
+
color: hasActiveFilter ? night.color.accent : night.color.textSecondary
|
|
594
595
|
}),
|
|
595
596
|
label: "Filters",
|
|
596
597
|
detail: hasActiveFilter ? "Active" : undefined,
|
|
@@ -602,7 +603,7 @@ function NetworkModalInner({
|
|
|
602
603
|
}), /*#__PURE__*/_jsx(NetworkHeaderMenuItem, {
|
|
603
604
|
icon: /*#__PURE__*/_jsx(Bookmark, {
|
|
604
605
|
size: 14,
|
|
605
|
-
color:
|
|
606
|
+
color: night.color.textSecondary
|
|
606
607
|
}),
|
|
607
608
|
label: "Saved requests",
|
|
608
609
|
onPress: () => {
|
|
@@ -654,6 +655,7 @@ function NetworkModalInner({
|
|
|
654
655
|
onClose: onClose,
|
|
655
656
|
onMinimize: onMinimize,
|
|
656
657
|
persistenceKey: persistenceKey,
|
|
658
|
+
variant: "night",
|
|
657
659
|
header: {
|
|
658
660
|
showToggleButton: true,
|
|
659
661
|
customContent: renderHeaderContent()
|
|
@@ -662,7 +664,12 @@ function NetworkModalInner({
|
|
|
662
664
|
enablePersistence: true,
|
|
663
665
|
initialMode: "bottomSheet",
|
|
664
666
|
enableGlitchEffects: true,
|
|
665
|
-
disableScrollWrapper: bodyOwnsScroll
|
|
667
|
+
disableScrollWrapper: bodyOwnsScroll
|
|
668
|
+
// Night hoists the ToolBackground to the sheet root (field runs through
|
|
669
|
+
// the header). The desktop INLINE panel never goes through JsModal — its
|
|
670
|
+
// host (the workbench) hoists one shared field instead, so the body
|
|
671
|
+
// mounts none of its own in either shell.
|
|
672
|
+
,
|
|
666
673
|
styles: {},
|
|
667
674
|
children: body
|
|
668
675
|
});
|
|
@@ -670,7 +677,10 @@ function NetworkModalInner({
|
|
|
670
677
|
const styles = StyleSheet.create({
|
|
671
678
|
container: {
|
|
672
679
|
flex: 1,
|
|
673
|
-
|
|
680
|
+
// Transparent so the HOST's hoisted star field shows through — the night
|
|
681
|
+
// JsModal's sheet-root field in the modal, the desktop workbench's shared
|
|
682
|
+
// row field in the inline panel.
|
|
683
|
+
backgroundColor: "transparent"
|
|
674
684
|
},
|
|
675
685
|
/** Detail = scrolling body + pinned stepper footer. `minHeight: 0` so the
|
|
676
686
|
* body's ScrollView scrolls instead of stretching the column. */
|
|
@@ -683,11 +693,13 @@ const styles = StyleSheet.create({
|
|
|
683
693
|
flex: 1
|
|
684
694
|
},
|
|
685
695
|
/** Inline shell — fills the host panel; `minHeight: 0` so the list's own
|
|
686
|
-
* FlatList can scroll instead of stretching the column.
|
|
696
|
+
* FlatList can scroll instead of stretching the column. Transparent so the
|
|
697
|
+
* desktop workbench's hoisted sky (one field spanning sidebar + panel)
|
|
698
|
+
* shows through — the shell paints its own base beneath that field. */
|
|
687
699
|
inlineRoot: {
|
|
688
700
|
flex: 1,
|
|
689
701
|
minHeight: 0,
|
|
690
|
-
backgroundColor:
|
|
702
|
+
backgroundColor: "transparent"
|
|
691
703
|
},
|
|
692
704
|
inlineHeader: {
|
|
693
705
|
paddingHorizontal: 12,
|
|
@@ -696,19 +708,21 @@ const styles = StyleSheet.create({
|
|
|
696
708
|
borderBottomColor: macOSColors.border.default,
|
|
697
709
|
backgroundColor: macOSColors.background.card
|
|
698
710
|
},
|
|
711
|
+
// Night chrome — the header is glass over the star field, so the search
|
|
712
|
+
// field and action chips sit on the Everlights dark-glass fill.
|
|
699
713
|
headerSearchContainer: {
|
|
700
714
|
flexDirection: "row",
|
|
701
715
|
alignItems: "center",
|
|
702
|
-
backgroundColor:
|
|
703
|
-
borderRadius:
|
|
716
|
+
backgroundColor: night.color.surfaceElevated,
|
|
717
|
+
borderRadius: night.radius.row,
|
|
704
718
|
borderWidth: 1,
|
|
705
|
-
borderColor:
|
|
719
|
+
borderColor: night.color.border,
|
|
706
720
|
paddingHorizontal: 12,
|
|
707
721
|
paddingVertical: 5
|
|
708
722
|
},
|
|
709
723
|
headerSearchInput: {
|
|
710
724
|
flex: 1,
|
|
711
|
-
color:
|
|
725
|
+
color: night.color.text,
|
|
712
726
|
fontSize: 13,
|
|
713
727
|
marginLeft: 6,
|
|
714
728
|
paddingVertical: 2
|
|
@@ -724,15 +738,15 @@ const styles = StyleSheet.create({
|
|
|
724
738
|
width: 32,
|
|
725
739
|
height: 32,
|
|
726
740
|
borderRadius: 8,
|
|
727
|
-
backgroundColor:
|
|
741
|
+
backgroundColor: night.color.buttonSurface,
|
|
728
742
|
borderWidth: 1,
|
|
729
|
-
borderColor:
|
|
743
|
+
borderColor: night.color.border,
|
|
730
744
|
alignItems: "center",
|
|
731
745
|
justifyContent: "center"
|
|
732
746
|
},
|
|
733
747
|
activeFilterButton: {
|
|
734
|
-
backgroundColor:
|
|
735
|
-
borderColor:
|
|
748
|
+
backgroundColor: night.color.accentSoft,
|
|
749
|
+
borderColor: night.color.accentBorderStrong
|
|
736
750
|
}
|
|
737
751
|
});
|
|
738
752
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { StyleSheet, Text, TouchableOpacity } from "react-native";
|
|
4
|
-
import {
|
|
4
|
+
import { night } from "@buoy-gg/shared-ui";
|
|
5
5
|
import { useOverrideRules } from "../hooks/useOverrideRules";
|
|
6
6
|
import { useSelectedNetworkEventId } from "../hooks/useNetworkSelection";
|
|
7
7
|
import { ruleMatches } from "../overrides/matchRule";
|
|
@@ -76,24 +76,27 @@ export function NetworkOverrideHeaderButton({
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
const styles = StyleSheet.create({
|
|
79
|
+
// Night: the header's dark-glass chip, with the accent label the night
|
|
80
|
+
// language gives labelled actions (plate + accent ink). Active keeps the
|
|
81
|
+
// warning tint — same "you are not seeing the truth" signal as the flask.
|
|
79
82
|
button: {
|
|
80
83
|
paddingHorizontal: 10,
|
|
81
84
|
paddingVertical: 6,
|
|
82
|
-
borderRadius:
|
|
85
|
+
borderRadius: 8,
|
|
83
86
|
borderWidth: 1,
|
|
84
|
-
borderColor:
|
|
85
|
-
backgroundColor:
|
|
87
|
+
borderColor: night.color.border,
|
|
88
|
+
backgroundColor: night.color.buttonSurface
|
|
86
89
|
},
|
|
87
90
|
buttonActive: {
|
|
88
|
-
borderColor:
|
|
89
|
-
backgroundColor:
|
|
91
|
+
borderColor: night.color.warning + "77",
|
|
92
|
+
backgroundColor: night.color.warningSoft
|
|
90
93
|
},
|
|
91
94
|
label: {
|
|
92
95
|
fontSize: 11,
|
|
93
96
|
fontWeight: "700",
|
|
94
|
-
color:
|
|
97
|
+
color: night.color.accent
|
|
95
98
|
},
|
|
96
99
|
labelActive: {
|
|
97
|
-
color:
|
|
100
|
+
color: night.color.warning
|
|
98
101
|
}
|
|
99
102
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
4
|
-
import { FlaskConical,
|
|
4
|
+
import { FlaskConical, night } from "@buoy-gg/shared-ui";
|
|
5
5
|
import { useOverrideRules } from "../hooks/useOverrideRules";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -32,7 +32,7 @@ export function NetworkOverridesButton({
|
|
|
32
32
|
accessibilityLabel: active ? `Response overrides — ${activeCount} active` : "Response overrides",
|
|
33
33
|
children: [/*#__PURE__*/_jsx(FlaskConical, {
|
|
34
34
|
size: 14,
|
|
35
|
-
color: active ?
|
|
35
|
+
color: active ? night.color.warning : night.color.textSecondary
|
|
36
36
|
}), active ? /*#__PURE__*/_jsx(View, {
|
|
37
37
|
style: styles.badge,
|
|
38
38
|
children: /*#__PURE__*/_jsx(Text, {
|
|
@@ -53,15 +53,15 @@ const styles = StyleSheet.create({
|
|
|
53
53
|
borderRadius: 8,
|
|
54
54
|
alignItems: "center",
|
|
55
55
|
justifyContent: "center",
|
|
56
|
-
backgroundColor:
|
|
56
|
+
backgroundColor: night.color.buttonSurface,
|
|
57
57
|
borderWidth: 1,
|
|
58
|
-
borderColor:
|
|
58
|
+
borderColor: night.color.border
|
|
59
59
|
},
|
|
60
60
|
// Active fills the whole box, the way the power toggle goes green — the
|
|
61
61
|
// warning now reads at a glance instead of as a tinted glyph.
|
|
62
62
|
buttonActive: {
|
|
63
|
-
backgroundColor:
|
|
64
|
-
borderColor:
|
|
63
|
+
backgroundColor: night.color.warningSoft,
|
|
64
|
+
borderColor: night.color.warning + "77"
|
|
65
65
|
},
|
|
66
66
|
badge: {
|
|
67
67
|
position: "absolute",
|
|
@@ -75,11 +75,11 @@ const styles = StyleSheet.create({
|
|
|
75
75
|
paddingHorizontal: 3,
|
|
76
76
|
alignItems: "center",
|
|
77
77
|
justifyContent: "center",
|
|
78
|
-
backgroundColor:
|
|
78
|
+
backgroundColor: night.color.warning
|
|
79
79
|
},
|
|
80
80
|
badgeText: {
|
|
81
81
|
fontSize: 8,
|
|
82
82
|
fontWeight: "800",
|
|
83
|
-
color:
|
|
83
|
+
color: "#000000"
|
|
84
84
|
}
|
|
85
85
|
});
|
|
@@ -5,7 +5,7 @@ import { flagsForEventId, networkSavedStore } from "../utils/networkSavedStore";
|
|
|
5
5
|
import { overrideRulesStore } from "../overrides/overrideRulesStore";
|
|
6
6
|
import { draftFromEvent } from "../overrides/presets";
|
|
7
7
|
import { inspectEngine } from "../overrides/engine";
|
|
8
|
-
import { getNetworkListenerCount, isNetworkListening } from "../utils/networkListener";
|
|
8
|
+
import { getNetworkListenerCount, isNetworkInterceptionLive, isNetworkListening } from "../utils/networkListener";
|
|
9
9
|
import { approxJsonSize, isOverWireBudget } from "@buoy-gg/shared-ui";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -361,22 +361,30 @@ export const networkSyncAdapter = {
|
|
|
361
361
|
// flags, and a total snapshot budget so the list degrades instead of the
|
|
362
362
|
// whole panel being dropped at the emit layer.
|
|
363
363
|
version: 5,
|
|
364
|
-
getSnapshot: () =>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
364
|
+
getSnapshot: () => {
|
|
365
|
+
// Piggyback the interception health check on the watch loop: while a
|
|
366
|
+
// dashboard/MCP watch is open this runs continuously, so a clobbered
|
|
367
|
+
// fetch/XHR patch (see networkListener.startListening) heals within one
|
|
368
|
+
// snapshot tick instead of staying dead for the session. Two property
|
|
369
|
+
// reads when healthy.
|
|
370
|
+
networkEventStore.ensureInterception();
|
|
371
|
+
return {
|
|
372
|
+
events: fitEventsToBudget(networkEventStore.getEvents()),
|
|
373
|
+
/**
|
|
374
|
+
* Response-override rules live on the DEVICE — that's the only place they
|
|
375
|
+
* can run, since a rule is applied inside the app's own XMLHttpRequest.
|
|
376
|
+
* Remote surfaces mirror this and forward their edits back as actions.
|
|
377
|
+
*/
|
|
378
|
+
overrides: stripLargeRuleBodies(overrideRulesStore.getState()),
|
|
379
|
+
// Saved snapshots go through the same body strip. They are few (capped in
|
|
380
|
+
// the store) but they ride on EVERY snapshot, so a fat one would land in
|
|
381
|
+
// the same 200ms loop that the body-stripping exists to protect.
|
|
382
|
+
saved: networkSavedStore.getRecords().map(record => ({
|
|
383
|
+
...record,
|
|
384
|
+
event: toWireEvent(record.event).event
|
|
385
|
+
}))
|
|
386
|
+
};
|
|
387
|
+
},
|
|
380
388
|
subscribe: onChange => {
|
|
381
389
|
// Pinning with no traffic in flight still has to reach the dashboard, so
|
|
382
390
|
// both stores drive the snapshot.
|
|
@@ -587,10 +595,22 @@ export const networkSyncAdapter = {
|
|
|
587
595
|
} catch {
|
|
588
596
|
/* no interceptable runtime here */
|
|
589
597
|
}
|
|
598
|
+
// `interceptorInstalled` is the listener's own flag; `interceptorLive`
|
|
599
|
+
// is whether the interceptors are actually reachable through the global
|
|
600
|
+
// chain. installed:true + live:false = something re-assigned fetch/XHR
|
|
601
|
+
// over the interceptor (it self-heals on the next subscribe/snapshot —
|
|
602
|
+
// see ensureInterception).
|
|
603
|
+
let interceptorLive = null;
|
|
604
|
+
try {
|
|
605
|
+
interceptorLive = isNetworkInterceptionLive();
|
|
606
|
+
} catch {
|
|
607
|
+
/* no interceptable runtime here */
|
|
608
|
+
}
|
|
590
609
|
return {
|
|
591
610
|
capturing: subscribers > 0,
|
|
592
611
|
subscribers,
|
|
593
612
|
interceptorInstalled,
|
|
613
|
+
interceptorLive,
|
|
594
614
|
listenerCount,
|
|
595
615
|
eventCount: networkEventStore.getEvents().length
|
|
596
616
|
};
|
|
@@ -172,6 +172,32 @@ class NetworkEventStore extends BaseEventStore {
|
|
|
172
172
|
return this.rawListenerUnsubscribe !== null;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Re-assert the interceptor if something re-assigned fetch/XHR since capture
|
|
177
|
+
* started (Expo's runtime, RN DevTools, a second dev server's tooling — see
|
|
178
|
+
* networkListener.startListening). A no-op costing two property reads when
|
|
179
|
+
* everything is healthy, and a no-op entirely unless this store is actively
|
|
180
|
+
* capturing — a mirror-mode dashboard must never install an interceptor in
|
|
181
|
+
* its own process.
|
|
182
|
+
*/
|
|
183
|
+
ensureInterception() {
|
|
184
|
+
if (!this.isCapturing()) return;
|
|
185
|
+
networkListener().startListening();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Every new consumer — the Network modal opening, a badge mounting, a
|
|
190
|
+
* dashboard watch attaching — is a chance to notice and repair a clobbered
|
|
191
|
+
* interceptor, which matters because a long-lived watch can hold the
|
|
192
|
+
* subscriber count above zero for the whole session (so the 0→1
|
|
193
|
+
* startCapturing path would never run again).
|
|
194
|
+
*/
|
|
195
|
+
subscribeToEvents(listener) {
|
|
196
|
+
const unsubscribe = super.subscribeToEvents(listener);
|
|
197
|
+
this.ensureInterception();
|
|
198
|
+
return unsubscribe;
|
|
199
|
+
}
|
|
200
|
+
|
|
175
201
|
/**
|
|
176
202
|
* Process a network listener event
|
|
177
203
|
*/
|