@buoy-gg/network 2.1.2 → 2.1.3
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/NetworkModal.js +9 -2
- package/lib/commonjs/network/hooks/useNetworkEvents.js +29 -54
- package/lib/commonjs/network/utils/networkEventStore.js +107 -83
- package/lib/module/network/components/NetworkModal.js +9 -2
- package/lib/module/network/hooks/useNetworkEvents.js +29 -54
- package/lib/module/network/utils/networkEventStore.js +108 -83
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/hooks/useNetworkEvents.d.ts +17 -10
- package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +1 -1
- package/lib/typescript/network/index.d.ts +1 -1
- package/lib/typescript/network/index.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkEventStore.d.ts +45 -39
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -49,8 +49,7 @@ function NetworkModalInner({
|
|
|
49
49
|
filter,
|
|
50
50
|
setFilter,
|
|
51
51
|
clearEvents,
|
|
52
|
-
|
|
53
|
-
toggleInterception,
|
|
52
|
+
isCapturing,
|
|
54
53
|
hasLockedEvents,
|
|
55
54
|
lockedEventCount,
|
|
56
55
|
isEventLocked,
|
|
@@ -58,6 +57,14 @@ function NetworkModalInner({
|
|
|
58
57
|
} = (0, _useNetworkEvents.useNetworkEvents)({
|
|
59
58
|
isPro
|
|
60
59
|
});
|
|
60
|
+
|
|
61
|
+
// Local UI state for display toggle (doesn't affect global event capture)
|
|
62
|
+
// This allows events tool to continue receiving events while this UI is "paused"
|
|
63
|
+
const [isDisplayPaused, setIsDisplayPaused] = (0, _react.useState)(false);
|
|
64
|
+
const isEnabled = isCapturing && !isDisplayPaused;
|
|
65
|
+
const toggleInterception = (0, _react.useCallback)(() => {
|
|
66
|
+
setIsDisplayPaused(prev => !prev);
|
|
67
|
+
}, []);
|
|
61
68
|
const handleModeChange = (0, _react.useCallback)(_mode => {
|
|
62
69
|
// Mode changes handled by JsModal
|
|
63
70
|
}, []);
|
|
@@ -7,11 +7,12 @@ exports.FREE_TIER_REQUEST_LIMIT = void 0;
|
|
|
7
7
|
exports.useNetworkEvents = useNetworkEvents;
|
|
8
8
|
var _react = require("react");
|
|
9
9
|
var _networkEventStore = require("../utils/networkEventStore");
|
|
10
|
-
var _networkListener = require("../utils/networkListener");
|
|
11
10
|
var _formatGraphQLVariables = require("../utils/formatGraphQLVariables");
|
|
12
11
|
/**
|
|
13
12
|
* Hook for accessing network events and controls
|
|
14
|
-
*
|
|
13
|
+
*
|
|
14
|
+
* Uses self-managing Subscribable pattern - the network listener automatically
|
|
15
|
+
* starts when this hook mounts and stops when all subscribers unmount.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
/** Free tier limit for network requests */
|
|
@@ -19,13 +20,15 @@ const FREE_TIER_REQUEST_LIMIT = exports.FREE_TIER_REQUEST_LIMIT = 25;
|
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Custom hook for accessing network events and controls
|
|
22
|
-
*
|
|
23
|
+
*
|
|
23
24
|
* This hook provides a complete interface for network monitoring, including
|
|
24
|
-
* event filtering, statistics calculation, and interception control.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* event filtering, statistics calculation, and interception control.
|
|
26
|
+
*
|
|
27
|
+
* The network listener automatically starts when this hook mounts and stops
|
|
28
|
+
* when all components using it unmount (Subscribable pattern from TanStack Query).
|
|
29
|
+
*
|
|
27
30
|
* @returns Object containing filtered events, statistics, controls, and utilities
|
|
28
|
-
*
|
|
31
|
+
*
|
|
29
32
|
* @example
|
|
30
33
|
* ```typescript
|
|
31
34
|
* function NetworkMonitor() {
|
|
@@ -35,22 +38,19 @@ const FREE_TIER_REQUEST_LIMIT = exports.FREE_TIER_REQUEST_LIMIT = 25;
|
|
|
35
38
|
* filter,
|
|
36
39
|
* setFilter,
|
|
37
40
|
* clearEvents,
|
|
38
|
-
*
|
|
39
|
-
* isEnabled
|
|
41
|
+
* isCapturing
|
|
40
42
|
* } = useNetworkEvents();
|
|
41
|
-
*
|
|
43
|
+
*
|
|
42
44
|
* return (
|
|
43
45
|
* <div>
|
|
44
46
|
* <p>Total requests: {stats.totalRequests}</p>
|
|
45
47
|
* <p>Success rate: {stats.successfulRequests}/{stats.totalRequests}</p>
|
|
46
|
-
* <
|
|
47
|
-
* {isEnabled ? 'Stop' : 'Start'} Monitoring
|
|
48
|
-
* </button>
|
|
48
|
+
* <p>Capturing: {isCapturing ? 'Yes' : 'No'}</p>
|
|
49
49
|
* </div>
|
|
50
50
|
* );
|
|
51
51
|
* }
|
|
52
52
|
* ```
|
|
53
|
-
*
|
|
53
|
+
*
|
|
54
54
|
* @performance Uses memoization for expensive filtering and statistics calculations
|
|
55
55
|
* @performance Optimizes string operations and array processing for large datasets
|
|
56
56
|
* @performance Includes Set-based lookups for O(1) filter matching
|
|
@@ -63,36 +63,17 @@ function useNetworkEvents(options = {}) {
|
|
|
63
63
|
} = options;
|
|
64
64
|
const [events, setEvents] = (0, _react.useState)([]);
|
|
65
65
|
const [filter, setFilter] = (0, _react.useState)({});
|
|
66
|
-
const [isEnabled, setIsEnabled] = (0, _react.useState)(false);
|
|
67
66
|
|
|
68
|
-
// Subscribe to event store
|
|
67
|
+
// Subscribe to event store - this automatically starts/stops the network listener
|
|
68
|
+
// based on subscriber count (Subscribable pattern from TanStack Query)
|
|
69
69
|
(0, _react.useEffect)(() => {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
// Only log in development and for non-ignored URLs
|
|
76
|
-
if (__DEV__ && !event.request.url.includes("symbolicate") && !event.request.url.includes(":8081")) {
|
|
77
|
-
// Network event processed: [event.type] [method] [url] - available for debugging if needed
|
|
78
|
-
}
|
|
79
|
-
_networkEventStore.networkEventStore.processNetworkEvent(event);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Check if already listening
|
|
83
|
-
setIsEnabled((0, _networkListener.networkListener)().isActive);
|
|
84
|
-
|
|
85
|
-
// Start listening if not already
|
|
86
|
-
if (!(0, _networkListener.networkListener)().isActive) {
|
|
87
|
-
(0, _networkListener.startNetworkListener)();
|
|
88
|
-
setIsEnabled(true);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Load initial events
|
|
92
|
-
setEvents(_networkEventStore.networkEventStore.getEvents());
|
|
70
|
+
// subscribeToEvents handles everything:
|
|
71
|
+
// - Starts network listener when first subscriber joins
|
|
72
|
+
// - Stops network listener when last subscriber leaves
|
|
73
|
+
// - Immediately provides current events
|
|
74
|
+
const unsubscribe = _networkEventStore.networkEventStore.subscribeToEvents(setEvents);
|
|
93
75
|
return () => {
|
|
94
|
-
|
|
95
|
-
unsubscribeListener();
|
|
76
|
+
unsubscribe();
|
|
96
77
|
};
|
|
97
78
|
}, []);
|
|
98
79
|
|
|
@@ -101,16 +82,8 @@ function useNetworkEvents(options = {}) {
|
|
|
101
82
|
_networkEventStore.networkEventStore.clearEvents();
|
|
102
83
|
}, []);
|
|
103
84
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
if (isEnabled) {
|
|
107
|
-
(0, _networkListener.stopNetworkListener)();
|
|
108
|
-
setIsEnabled(false);
|
|
109
|
-
} else {
|
|
110
|
-
(0, _networkListener.startNetworkListener)();
|
|
111
|
-
setIsEnabled(true);
|
|
112
|
-
}
|
|
113
|
-
}, [isEnabled]);
|
|
85
|
+
// Check if the store is currently capturing (has subscribers)
|
|
86
|
+
const isCapturing = _networkEventStore.networkEventStore.isCapturing();
|
|
114
87
|
|
|
115
88
|
// Memoize search text processing to avoid repeated toLowerCase calls
|
|
116
89
|
// Performance: Expensive string operations repeated for every event on every filter
|
|
@@ -304,8 +277,8 @@ function useNetworkEvents(options = {}) {
|
|
|
304
277
|
filter,
|
|
305
278
|
setFilter,
|
|
306
279
|
clearEvents,
|
|
307
|
-
|
|
308
|
-
|
|
280
|
+
/** Whether the store has subscribers (listener is running) */
|
|
281
|
+
isCapturing,
|
|
309
282
|
hosts,
|
|
310
283
|
methods,
|
|
311
284
|
/** Whether there are locked events due to free tier limit */
|
|
@@ -315,6 +288,8 @@ function useNetworkEvents(options = {}) {
|
|
|
315
288
|
/** Check if a specific event is locked (by ID) */
|
|
316
289
|
isEventLocked,
|
|
317
290
|
/** Free tier request limit */
|
|
318
|
-
requestLimit: FREE_TIER_REQUEST_LIMIT
|
|
291
|
+
requestLimit: FREE_TIER_REQUEST_LIMIT,
|
|
292
|
+
/** Get subscriber counts for debugging */
|
|
293
|
+
getSubscriberCounts: () => _networkEventStore.networkEventStore.getSubscriberCounts()
|
|
319
294
|
};
|
|
320
295
|
}
|
|
@@ -4,18 +4,91 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.networkEventStore = void 0;
|
|
7
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
7
8
|
var _extractOperationName = require("./extractOperationName");
|
|
9
|
+
var _networkListener = require("./networkListener");
|
|
8
10
|
/**
|
|
9
|
-
* Network
|
|
10
|
-
*
|
|
11
|
+
* Network Event Store
|
|
12
|
+
*
|
|
13
|
+
* Store for managing captured network requests. Uses BaseEventStore pattern -
|
|
14
|
+
* the network listener automatically starts when the first subscriber joins
|
|
15
|
+
* and stops when the last subscriber leaves.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { networkEventStore } from '@buoy-gg/network';
|
|
20
|
+
*
|
|
21
|
+
* // Subscribe to network events - automatically starts capturing!
|
|
22
|
+
* const unsubscribe = networkEventStore.subscribeToEvents((events) => {
|
|
23
|
+
* console.log('Network events:', events);
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Later, clean up - automatically stops if no other subscribers
|
|
27
|
+
* unsubscribe();
|
|
28
|
+
* ```
|
|
11
29
|
*/
|
|
12
30
|
|
|
13
|
-
|
|
14
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Callback type for individual event notifications
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Callback type for full events array notifications
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
class NetworkEventStore extends _sharedUi.BaseEventStore {
|
|
15
40
|
pendingRequests = new Map();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
recentRequests = new Map();
|
|
42
|
+
rawListenerUnsubscribe = null;
|
|
43
|
+
constructor() {
|
|
44
|
+
super({
|
|
45
|
+
storeName: "network",
|
|
46
|
+
maxEvents: 500
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Start capturing network events
|
|
52
|
+
*/
|
|
53
|
+
startCapturing() {
|
|
54
|
+
const listener = (0, _networkListener.networkListener)();
|
|
55
|
+
|
|
56
|
+
// Start the network interceptor if not already active
|
|
57
|
+
if (!listener.isActive) {
|
|
58
|
+
listener.startListening();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add our listener to push raw events into the store
|
|
62
|
+
if (!this.rawListenerUnsubscribe) {
|
|
63
|
+
this.rawListenerUnsubscribe = (0, _networkListener.addNetworkListener)(event => {
|
|
64
|
+
this.processNetworkEvent(event);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Stop capturing network events
|
|
71
|
+
*/
|
|
72
|
+
stopCapturing() {
|
|
73
|
+
// Remove our raw listener
|
|
74
|
+
if (this.rawListenerUnsubscribe) {
|
|
75
|
+
this.rawListenerUnsubscribe();
|
|
76
|
+
this.rawListenerUnsubscribe = null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// If no other listeners exist, stop the network interceptor
|
|
80
|
+
const listener = (0, _networkListener.networkListener)();
|
|
81
|
+
if (listener.listenerCount === 0) {
|
|
82
|
+
listener.stopListening();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if the store is actively capturing network events
|
|
88
|
+
*/
|
|
89
|
+
isCapturing() {
|
|
90
|
+
return this.rawListenerUnsubscribe !== null;
|
|
91
|
+
}
|
|
19
92
|
|
|
20
93
|
/**
|
|
21
94
|
* Process a network listener event
|
|
@@ -32,13 +105,13 @@ class NetworkEventStore {
|
|
|
32
105
|
|
|
33
106
|
// If same request within 50ms, likely a duplicate from XHR/fetch dual interception
|
|
34
107
|
if (lastRequestTime && now - lastRequestTime < 50) {
|
|
35
|
-
return;
|
|
108
|
+
return;
|
|
36
109
|
}
|
|
37
110
|
this.recentRequests.set(requestKey, now);
|
|
38
111
|
|
|
39
112
|
// Clean up old entries to prevent memory leak
|
|
40
113
|
if (this.recentRequests.size > 100) {
|
|
41
|
-
const cutoff = now - 5000;
|
|
114
|
+
const cutoff = now - 5000;
|
|
42
115
|
for (const [key, time] of this.recentRequests.entries()) {
|
|
43
116
|
if (time < cutoff) {
|
|
44
117
|
this.recentRequests.delete(key);
|
|
@@ -46,7 +119,6 @@ class NetworkEventStore {
|
|
|
46
119
|
}
|
|
47
120
|
}
|
|
48
121
|
|
|
49
|
-
// Create new network event for request
|
|
50
122
|
// Build full URL with query params if present
|
|
51
123
|
const queryString = request.params ? `?${new URLSearchParams(request.params).toString()}` : "";
|
|
52
124
|
const fullUrl = `${request.url}${queryString}`;
|
|
@@ -54,12 +126,10 @@ class NetworkEventStore {
|
|
|
54
126
|
// Extract GraphQL operation name and variables for searchability
|
|
55
127
|
let operationName;
|
|
56
128
|
let graphqlVariables;
|
|
57
|
-
if (request.client ===
|
|
129
|
+
if (request.client === "graphql") {
|
|
58
130
|
const extracted = (0, _extractOperationName.extractOperationName)(request.data);
|
|
59
131
|
operationName = extracted || undefined;
|
|
60
|
-
|
|
61
|
-
// Extract variables from GraphQL request
|
|
62
|
-
if (request.data && typeof request.data === 'object' && 'variables' in request.data && request.data.variables && typeof request.data.variables === 'object') {
|
|
132
|
+
if (request.data && typeof request.data === "object" && "variables" in request.data && request.data.variables && typeof request.data.variables === "object") {
|
|
63
133
|
graphqlVariables = request.data.variables;
|
|
64
134
|
}
|
|
65
135
|
}
|
|
@@ -67,7 +137,6 @@ class NetworkEventStore {
|
|
|
67
137
|
id: request.id,
|
|
68
138
|
method: request.method,
|
|
69
139
|
url: fullUrl,
|
|
70
|
-
// Store FULL URL with query params
|
|
71
140
|
host: this.extractHost(request.url),
|
|
72
141
|
path: this.extractPath(request.url),
|
|
73
142
|
query: queryString,
|
|
@@ -78,18 +147,13 @@ class NetworkEventStore {
|
|
|
78
147
|
responseHeaders: {},
|
|
79
148
|
requestClient: request.client,
|
|
80
149
|
operationName,
|
|
81
|
-
|
|
82
|
-
graphqlVariables // GraphQL variables for display and search
|
|
150
|
+
graphqlVariables
|
|
83
151
|
};
|
|
84
|
-
|
|
85
|
-
// Store as pending
|
|
86
152
|
this.pendingRequests.set(request.id, networkEvent);
|
|
87
|
-
|
|
88
|
-
// Add to events list
|
|
89
153
|
this.events = [networkEvent, ...this.events].slice(0, this.maxEvents);
|
|
90
|
-
this.
|
|
154
|
+
this.notify(networkEvent);
|
|
155
|
+
this.notifyArrayListeners();
|
|
91
156
|
} else if (event.type === "response" || event.type === "error") {
|
|
92
|
-
// Find and update the pending request
|
|
93
157
|
const index = this.events.findIndex(e => e.id === request.id);
|
|
94
158
|
if (index !== -1) {
|
|
95
159
|
const updatedEvent = {
|
|
@@ -110,14 +174,11 @@ class NetworkEventStore {
|
|
|
110
174
|
}
|
|
111
175
|
this.events[index] = updatedEvent;
|
|
112
176
|
this.pendingRequests.delete(request.id);
|
|
113
|
-
this.
|
|
177
|
+
this.notify(updatedEvent);
|
|
178
|
+
this.notifyArrayListeners();
|
|
114
179
|
}
|
|
115
180
|
}
|
|
116
181
|
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Extract host from URL
|
|
120
|
-
*/
|
|
121
182
|
extractHost(url) {
|
|
122
183
|
try {
|
|
123
184
|
const urlObj = new URL(url);
|
|
@@ -127,10 +188,6 @@ class NetworkEventStore {
|
|
|
127
188
|
return "";
|
|
128
189
|
}
|
|
129
190
|
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Extract path from URL
|
|
133
|
-
*/
|
|
134
191
|
extractPath(url) {
|
|
135
192
|
try {
|
|
136
193
|
const urlObj = new URL(url);
|
|
@@ -140,10 +197,6 @@ class NetworkEventStore {
|
|
|
140
197
|
return url;
|
|
141
198
|
}
|
|
142
199
|
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Get size of data
|
|
146
|
-
*/
|
|
147
200
|
getDataSize(data) {
|
|
148
201
|
if (!data) return 0;
|
|
149
202
|
if (typeof data === "string") return data.length;
|
|
@@ -153,63 +206,38 @@ class NetworkEventStore {
|
|
|
153
206
|
return 0;
|
|
154
207
|
}
|
|
155
208
|
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Get all events
|
|
159
|
-
*/
|
|
160
|
-
getEvents() {
|
|
161
|
-
return [...this.events];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get event by ID
|
|
166
|
-
*/
|
|
167
209
|
getEventById(id) {
|
|
168
210
|
return this.events.find(e => e.id === id);
|
|
169
211
|
}
|
|
170
212
|
|
|
171
213
|
/**
|
|
172
|
-
*
|
|
214
|
+
* Override clearEvents to also clear pending requests
|
|
173
215
|
*/
|
|
174
216
|
clearEvents() {
|
|
175
|
-
|
|
217
|
+
super.clearEvents();
|
|
176
218
|
this.pendingRequests.clear();
|
|
177
219
|
this.recentRequests.clear();
|
|
178
|
-
this.notifyListeners();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Subscribe to event changes
|
|
183
|
-
*/
|
|
184
|
-
subscribe(listener) {
|
|
185
|
-
this.listeners.add(listener);
|
|
186
|
-
return () => {
|
|
187
|
-
this.listeners.delete(listener);
|
|
188
|
-
};
|
|
189
220
|
}
|
|
190
221
|
|
|
191
222
|
/**
|
|
192
|
-
*
|
|
223
|
+
* Subscribe to individual events with optional replay of existing events
|
|
193
224
|
*/
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
this.listeners.forEach(listener => listener(events));
|
|
197
|
-
}
|
|
225
|
+
onEvent(callback, replayExisting = true) {
|
|
226
|
+
const unsubscribe = this.subscribe(callback);
|
|
198
227
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
// Replay existing events if requested
|
|
229
|
+
if (replayExisting && this.events.length > 0) {
|
|
230
|
+
const existingEvents = [...this.events].reverse();
|
|
231
|
+
for (const event of existingEvents) {
|
|
232
|
+
try {
|
|
233
|
+
callback(event);
|
|
234
|
+
} catch {
|
|
235
|
+
// Ignore callback errors
|
|
236
|
+
}
|
|
237
|
+
}
|
|
207
238
|
}
|
|
239
|
+
return unsubscribe;
|
|
208
240
|
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Get statistics about network events
|
|
212
|
-
*/
|
|
213
241
|
getStats() {
|
|
214
242
|
const total = this.events.length;
|
|
215
243
|
const successful = this.events.filter(e => e.status && e.status >= 200 && e.status < 300).length;
|
|
@@ -229,10 +257,6 @@ class NetworkEventStore {
|
|
|
229
257
|
averageDuration: Math.round(avgDuration)
|
|
230
258
|
};
|
|
231
259
|
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Filter events by criteria
|
|
235
|
-
*/
|
|
236
260
|
filterEvents(filter) {
|
|
237
261
|
let filtered = [...this.events];
|
|
238
262
|
if (filter.method) {
|
|
@@ -263,7 +287,7 @@ class NetworkEventStore {
|
|
|
263
287
|
}
|
|
264
288
|
|
|
265
289
|
/**
|
|
266
|
-
* Singleton store that aggregates captured network traffic.
|
|
267
|
-
*
|
|
290
|
+
* Singleton store that aggregates captured network traffic.
|
|
291
|
+
* Automatically starts/stops capturing based on subscriber count.
|
|
268
292
|
*/
|
|
269
293
|
const networkEventStore = exports.networkEventStore = new NetworkEventStore();
|
|
@@ -45,8 +45,7 @@ function NetworkModalInner({
|
|
|
45
45
|
filter,
|
|
46
46
|
setFilter,
|
|
47
47
|
clearEvents,
|
|
48
|
-
|
|
49
|
-
toggleInterception,
|
|
48
|
+
isCapturing,
|
|
50
49
|
hasLockedEvents,
|
|
51
50
|
lockedEventCount,
|
|
52
51
|
isEventLocked,
|
|
@@ -54,6 +53,14 @@ function NetworkModalInner({
|
|
|
54
53
|
} = useNetworkEvents({
|
|
55
54
|
isPro
|
|
56
55
|
});
|
|
56
|
+
|
|
57
|
+
// Local UI state for display toggle (doesn't affect global event capture)
|
|
58
|
+
// This allows events tool to continue receiving events while this UI is "paused"
|
|
59
|
+
const [isDisplayPaused, setIsDisplayPaused] = useState(false);
|
|
60
|
+
const isEnabled = isCapturing && !isDisplayPaused;
|
|
61
|
+
const toggleInterception = useCallback(() => {
|
|
62
|
+
setIsDisplayPaused(prev => !prev);
|
|
63
|
+
}, []);
|
|
57
64
|
const handleModeChange = useCallback(_mode => {
|
|
58
65
|
// Mode changes handled by JsModal
|
|
59
66
|
}, []);
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Hook for accessing network events and controls
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Uses self-managing Subscribable pattern - the network listener automatically
|
|
7
|
+
* starts when this hook mounts and stops when all subscribers unmount.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
9
11
|
import { networkEventStore } from "../utils/networkEventStore";
|
|
10
|
-
import { networkListener, startNetworkListener, stopNetworkListener, addNetworkListener } from "../utils/networkListener";
|
|
11
12
|
import { searchGraphQLVariables } from "../utils/formatGraphQLVariables";
|
|
12
13
|
|
|
13
14
|
/** Free tier limit for network requests */
|
|
@@ -15,13 +16,15 @@ export const FREE_TIER_REQUEST_LIMIT = 25;
|
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Custom hook for accessing network events and controls
|
|
18
|
-
*
|
|
19
|
+
*
|
|
19
20
|
* This hook provides a complete interface for network monitoring, including
|
|
20
|
-
* event filtering, statistics calculation, and interception control.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* event filtering, statistics calculation, and interception control.
|
|
22
|
+
*
|
|
23
|
+
* The network listener automatically starts when this hook mounts and stops
|
|
24
|
+
* when all components using it unmount (Subscribable pattern from TanStack Query).
|
|
25
|
+
*
|
|
23
26
|
* @returns Object containing filtered events, statistics, controls, and utilities
|
|
24
|
-
*
|
|
27
|
+
*
|
|
25
28
|
* @example
|
|
26
29
|
* ```typescript
|
|
27
30
|
* function NetworkMonitor() {
|
|
@@ -31,22 +34,19 @@ export const FREE_TIER_REQUEST_LIMIT = 25;
|
|
|
31
34
|
* filter,
|
|
32
35
|
* setFilter,
|
|
33
36
|
* clearEvents,
|
|
34
|
-
*
|
|
35
|
-
* isEnabled
|
|
37
|
+
* isCapturing
|
|
36
38
|
* } = useNetworkEvents();
|
|
37
|
-
*
|
|
39
|
+
*
|
|
38
40
|
* return (
|
|
39
41
|
* <div>
|
|
40
42
|
* <p>Total requests: {stats.totalRequests}</p>
|
|
41
43
|
* <p>Success rate: {stats.successfulRequests}/{stats.totalRequests}</p>
|
|
42
|
-
* <
|
|
43
|
-
* {isEnabled ? 'Stop' : 'Start'} Monitoring
|
|
44
|
-
* </button>
|
|
44
|
+
* <p>Capturing: {isCapturing ? 'Yes' : 'No'}</p>
|
|
45
45
|
* </div>
|
|
46
46
|
* );
|
|
47
47
|
* }
|
|
48
48
|
* ```
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
50
|
* @performance Uses memoization for expensive filtering and statistics calculations
|
|
51
51
|
* @performance Optimizes string operations and array processing for large datasets
|
|
52
52
|
* @performance Includes Set-based lookups for O(1) filter matching
|
|
@@ -59,36 +59,17 @@ export function useNetworkEvents(options = {}) {
|
|
|
59
59
|
} = options;
|
|
60
60
|
const [events, setEvents] = useState([]);
|
|
61
61
|
const [filter, setFilter] = useState({});
|
|
62
|
-
const [isEnabled, setIsEnabled] = useState(false);
|
|
63
62
|
|
|
64
|
-
// Subscribe to event store
|
|
63
|
+
// Subscribe to event store - this automatically starts/stops the network listener
|
|
64
|
+
// based on subscriber count (Subscribable pattern from TanStack Query)
|
|
65
65
|
useEffect(() => {
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
const
|
|
71
|
-
// Only log in development and for non-ignored URLs
|
|
72
|
-
if (__DEV__ && !event.request.url.includes("symbolicate") && !event.request.url.includes(":8081")) {
|
|
73
|
-
// Network event processed: [event.type] [method] [url] - available for debugging if needed
|
|
74
|
-
}
|
|
75
|
-
networkEventStore.processNetworkEvent(event);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Check if already listening
|
|
79
|
-
setIsEnabled(networkListener().isActive);
|
|
80
|
-
|
|
81
|
-
// Start listening if not already
|
|
82
|
-
if (!networkListener().isActive) {
|
|
83
|
-
startNetworkListener();
|
|
84
|
-
setIsEnabled(true);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Load initial events
|
|
88
|
-
setEvents(networkEventStore.getEvents());
|
|
66
|
+
// subscribeToEvents handles everything:
|
|
67
|
+
// - Starts network listener when first subscriber joins
|
|
68
|
+
// - Stops network listener when last subscriber leaves
|
|
69
|
+
// - Immediately provides current events
|
|
70
|
+
const unsubscribe = networkEventStore.subscribeToEvents(setEvents);
|
|
89
71
|
return () => {
|
|
90
|
-
|
|
91
|
-
unsubscribeListener();
|
|
72
|
+
unsubscribe();
|
|
92
73
|
};
|
|
93
74
|
}, []);
|
|
94
75
|
|
|
@@ -97,16 +78,8 @@ export function useNetworkEvents(options = {}) {
|
|
|
97
78
|
networkEventStore.clearEvents();
|
|
98
79
|
}, []);
|
|
99
80
|
|
|
100
|
-
//
|
|
101
|
-
const
|
|
102
|
-
if (isEnabled) {
|
|
103
|
-
stopNetworkListener();
|
|
104
|
-
setIsEnabled(false);
|
|
105
|
-
} else {
|
|
106
|
-
startNetworkListener();
|
|
107
|
-
setIsEnabled(true);
|
|
108
|
-
}
|
|
109
|
-
}, [isEnabled]);
|
|
81
|
+
// Check if the store is currently capturing (has subscribers)
|
|
82
|
+
const isCapturing = networkEventStore.isCapturing();
|
|
110
83
|
|
|
111
84
|
// Memoize search text processing to avoid repeated toLowerCase calls
|
|
112
85
|
// Performance: Expensive string operations repeated for every event on every filter
|
|
@@ -300,8 +273,8 @@ export function useNetworkEvents(options = {}) {
|
|
|
300
273
|
filter,
|
|
301
274
|
setFilter,
|
|
302
275
|
clearEvents,
|
|
303
|
-
|
|
304
|
-
|
|
276
|
+
/** Whether the store has subscribers (listener is running) */
|
|
277
|
+
isCapturing,
|
|
305
278
|
hosts,
|
|
306
279
|
methods,
|
|
307
280
|
/** Whether there are locked events due to free tier limit */
|
|
@@ -311,6 +284,8 @@ export function useNetworkEvents(options = {}) {
|
|
|
311
284
|
/** Check if a specific event is locked (by ID) */
|
|
312
285
|
isEventLocked,
|
|
313
286
|
/** Free tier request limit */
|
|
314
|
-
requestLimit: FREE_TIER_REQUEST_LIMIT
|
|
287
|
+
requestLimit: FREE_TIER_REQUEST_LIMIT,
|
|
288
|
+
/** Get subscriber counts for debugging */
|
|
289
|
+
getSubscriberCounts: () => networkEventStore.getSubscriberCounts()
|
|
315
290
|
};
|
|
316
291
|
}
|
|
@@ -1,17 +1,91 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Network
|
|
5
|
-
*
|
|
4
|
+
* Network Event Store
|
|
5
|
+
*
|
|
6
|
+
* Store for managing captured network requests. Uses BaseEventStore pattern -
|
|
7
|
+
* the network listener automatically starts when the first subscriber joins
|
|
8
|
+
* and stops when the last subscriber leaves.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { networkEventStore } from '@buoy-gg/network';
|
|
13
|
+
*
|
|
14
|
+
* // Subscribe to network events - automatically starts capturing!
|
|
15
|
+
* const unsubscribe = networkEventStore.subscribeToEvents((events) => {
|
|
16
|
+
* console.log('Network events:', events);
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Later, clean up - automatically stops if no other subscribers
|
|
20
|
+
* unsubscribe();
|
|
21
|
+
* ```
|
|
6
22
|
*/
|
|
7
23
|
|
|
24
|
+
import { BaseEventStore } from "@buoy-gg/shared-ui";
|
|
8
25
|
import { extractOperationName } from "./extractOperationName";
|
|
9
|
-
|
|
10
|
-
|
|
26
|
+
import { networkListener, addNetworkListener } from "./networkListener";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Callback type for individual event notifications
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Callback type for full events array notifications
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
class NetworkEventStore extends BaseEventStore {
|
|
11
37
|
pendingRequests = new Map();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
38
|
+
recentRequests = new Map();
|
|
39
|
+
rawListenerUnsubscribe = null;
|
|
40
|
+
constructor() {
|
|
41
|
+
super({
|
|
42
|
+
storeName: "network",
|
|
43
|
+
maxEvents: 500
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Start capturing network events
|
|
49
|
+
*/
|
|
50
|
+
startCapturing() {
|
|
51
|
+
const listener = networkListener();
|
|
52
|
+
|
|
53
|
+
// Start the network interceptor if not already active
|
|
54
|
+
if (!listener.isActive) {
|
|
55
|
+
listener.startListening();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add our listener to push raw events into the store
|
|
59
|
+
if (!this.rawListenerUnsubscribe) {
|
|
60
|
+
this.rawListenerUnsubscribe = addNetworkListener(event => {
|
|
61
|
+
this.processNetworkEvent(event);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stop capturing network events
|
|
68
|
+
*/
|
|
69
|
+
stopCapturing() {
|
|
70
|
+
// Remove our raw listener
|
|
71
|
+
if (this.rawListenerUnsubscribe) {
|
|
72
|
+
this.rawListenerUnsubscribe();
|
|
73
|
+
this.rawListenerUnsubscribe = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If no other listeners exist, stop the network interceptor
|
|
77
|
+
const listener = networkListener();
|
|
78
|
+
if (listener.listenerCount === 0) {
|
|
79
|
+
listener.stopListening();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if the store is actively capturing network events
|
|
85
|
+
*/
|
|
86
|
+
isCapturing() {
|
|
87
|
+
return this.rawListenerUnsubscribe !== null;
|
|
88
|
+
}
|
|
15
89
|
|
|
16
90
|
/**
|
|
17
91
|
* Process a network listener event
|
|
@@ -28,13 +102,13 @@ class NetworkEventStore {
|
|
|
28
102
|
|
|
29
103
|
// If same request within 50ms, likely a duplicate from XHR/fetch dual interception
|
|
30
104
|
if (lastRequestTime && now - lastRequestTime < 50) {
|
|
31
|
-
return;
|
|
105
|
+
return;
|
|
32
106
|
}
|
|
33
107
|
this.recentRequests.set(requestKey, now);
|
|
34
108
|
|
|
35
109
|
// Clean up old entries to prevent memory leak
|
|
36
110
|
if (this.recentRequests.size > 100) {
|
|
37
|
-
const cutoff = now - 5000;
|
|
111
|
+
const cutoff = now - 5000;
|
|
38
112
|
for (const [key, time] of this.recentRequests.entries()) {
|
|
39
113
|
if (time < cutoff) {
|
|
40
114
|
this.recentRequests.delete(key);
|
|
@@ -42,7 +116,6 @@ class NetworkEventStore {
|
|
|
42
116
|
}
|
|
43
117
|
}
|
|
44
118
|
|
|
45
|
-
// Create new network event for request
|
|
46
119
|
// Build full URL with query params if present
|
|
47
120
|
const queryString = request.params ? `?${new URLSearchParams(request.params).toString()}` : "";
|
|
48
121
|
const fullUrl = `${request.url}${queryString}`;
|
|
@@ -50,12 +123,10 @@ class NetworkEventStore {
|
|
|
50
123
|
// Extract GraphQL operation name and variables for searchability
|
|
51
124
|
let operationName;
|
|
52
125
|
let graphqlVariables;
|
|
53
|
-
if (request.client ===
|
|
126
|
+
if (request.client === "graphql") {
|
|
54
127
|
const extracted = extractOperationName(request.data);
|
|
55
128
|
operationName = extracted || undefined;
|
|
56
|
-
|
|
57
|
-
// Extract variables from GraphQL request
|
|
58
|
-
if (request.data && typeof request.data === 'object' && 'variables' in request.data && request.data.variables && typeof request.data.variables === 'object') {
|
|
129
|
+
if (request.data && typeof request.data === "object" && "variables" in request.data && request.data.variables && typeof request.data.variables === "object") {
|
|
59
130
|
graphqlVariables = request.data.variables;
|
|
60
131
|
}
|
|
61
132
|
}
|
|
@@ -63,7 +134,6 @@ class NetworkEventStore {
|
|
|
63
134
|
id: request.id,
|
|
64
135
|
method: request.method,
|
|
65
136
|
url: fullUrl,
|
|
66
|
-
// Store FULL URL with query params
|
|
67
137
|
host: this.extractHost(request.url),
|
|
68
138
|
path: this.extractPath(request.url),
|
|
69
139
|
query: queryString,
|
|
@@ -74,18 +144,13 @@ class NetworkEventStore {
|
|
|
74
144
|
responseHeaders: {},
|
|
75
145
|
requestClient: request.client,
|
|
76
146
|
operationName,
|
|
77
|
-
|
|
78
|
-
graphqlVariables // GraphQL variables for display and search
|
|
147
|
+
graphqlVariables
|
|
79
148
|
};
|
|
80
|
-
|
|
81
|
-
// Store as pending
|
|
82
149
|
this.pendingRequests.set(request.id, networkEvent);
|
|
83
|
-
|
|
84
|
-
// Add to events list
|
|
85
150
|
this.events = [networkEvent, ...this.events].slice(0, this.maxEvents);
|
|
86
|
-
this.
|
|
151
|
+
this.notify(networkEvent);
|
|
152
|
+
this.notifyArrayListeners();
|
|
87
153
|
} else if (event.type === "response" || event.type === "error") {
|
|
88
|
-
// Find and update the pending request
|
|
89
154
|
const index = this.events.findIndex(e => e.id === request.id);
|
|
90
155
|
if (index !== -1) {
|
|
91
156
|
const updatedEvent = {
|
|
@@ -106,14 +171,11 @@ class NetworkEventStore {
|
|
|
106
171
|
}
|
|
107
172
|
this.events[index] = updatedEvent;
|
|
108
173
|
this.pendingRequests.delete(request.id);
|
|
109
|
-
this.
|
|
174
|
+
this.notify(updatedEvent);
|
|
175
|
+
this.notifyArrayListeners();
|
|
110
176
|
}
|
|
111
177
|
}
|
|
112
178
|
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Extract host from URL
|
|
116
|
-
*/
|
|
117
179
|
extractHost(url) {
|
|
118
180
|
try {
|
|
119
181
|
const urlObj = new URL(url);
|
|
@@ -123,10 +185,6 @@ class NetworkEventStore {
|
|
|
123
185
|
return "";
|
|
124
186
|
}
|
|
125
187
|
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Extract path from URL
|
|
129
|
-
*/
|
|
130
188
|
extractPath(url) {
|
|
131
189
|
try {
|
|
132
190
|
const urlObj = new URL(url);
|
|
@@ -136,10 +194,6 @@ class NetworkEventStore {
|
|
|
136
194
|
return url;
|
|
137
195
|
}
|
|
138
196
|
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Get size of data
|
|
142
|
-
*/
|
|
143
197
|
getDataSize(data) {
|
|
144
198
|
if (!data) return 0;
|
|
145
199
|
if (typeof data === "string") return data.length;
|
|
@@ -149,63 +203,38 @@ class NetworkEventStore {
|
|
|
149
203
|
return 0;
|
|
150
204
|
}
|
|
151
205
|
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get all events
|
|
155
|
-
*/
|
|
156
|
-
getEvents() {
|
|
157
|
-
return [...this.events];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Get event by ID
|
|
162
|
-
*/
|
|
163
206
|
getEventById(id) {
|
|
164
207
|
return this.events.find(e => e.id === id);
|
|
165
208
|
}
|
|
166
209
|
|
|
167
210
|
/**
|
|
168
|
-
*
|
|
211
|
+
* Override clearEvents to also clear pending requests
|
|
169
212
|
*/
|
|
170
213
|
clearEvents() {
|
|
171
|
-
|
|
214
|
+
super.clearEvents();
|
|
172
215
|
this.pendingRequests.clear();
|
|
173
216
|
this.recentRequests.clear();
|
|
174
|
-
this.notifyListeners();
|
|
175
217
|
}
|
|
176
218
|
|
|
177
219
|
/**
|
|
178
|
-
* Subscribe to
|
|
220
|
+
* Subscribe to individual events with optional replay of existing events
|
|
179
221
|
*/
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
return () => {
|
|
183
|
-
this.listeners.delete(listener);
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Notify all listeners of changes
|
|
189
|
-
*/
|
|
190
|
-
notifyListeners() {
|
|
191
|
-
const events = this.getEvents();
|
|
192
|
-
this.listeners.forEach(listener => listener(events));
|
|
193
|
-
}
|
|
222
|
+
onEvent(callback, replayExisting = true) {
|
|
223
|
+
const unsubscribe = this.subscribe(callback);
|
|
194
224
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
225
|
+
// Replay existing events if requested
|
|
226
|
+
if (replayExisting && this.events.length > 0) {
|
|
227
|
+
const existingEvents = [...this.events].reverse();
|
|
228
|
+
for (const event of existingEvents) {
|
|
229
|
+
try {
|
|
230
|
+
callback(event);
|
|
231
|
+
} catch {
|
|
232
|
+
// Ignore callback errors
|
|
233
|
+
}
|
|
234
|
+
}
|
|
203
235
|
}
|
|
236
|
+
return unsubscribe;
|
|
204
237
|
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Get statistics about network events
|
|
208
|
-
*/
|
|
209
238
|
getStats() {
|
|
210
239
|
const total = this.events.length;
|
|
211
240
|
const successful = this.events.filter(e => e.status && e.status >= 200 && e.status < 300).length;
|
|
@@ -225,10 +254,6 @@ class NetworkEventStore {
|
|
|
225
254
|
averageDuration: Math.round(avgDuration)
|
|
226
255
|
};
|
|
227
256
|
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Filter events by criteria
|
|
231
|
-
*/
|
|
232
257
|
filterEvents(filter) {
|
|
233
258
|
let filtered = [...this.events];
|
|
234
259
|
if (filter.method) {
|
|
@@ -259,7 +284,7 @@ class NetworkEventStore {
|
|
|
259
284
|
}
|
|
260
285
|
|
|
261
286
|
/**
|
|
262
|
-
* Singleton store that aggregates captured network traffic.
|
|
263
|
-
*
|
|
287
|
+
* Singleton store that aggregates captured network traffic.
|
|
288
|
+
* Automatically starts/stops capturing based on subscriber count.
|
|
264
289
|
*/
|
|
265
290
|
export const networkEventStore = new NetworkEventStore();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkModal.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkModal.tsx"],"names":[],"mappings":"AA4CA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;
|
|
1
|
+
{"version":3,"file":"NetworkModal.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkModal.tsx"],"names":[],"mappings":"AA4CA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAkhCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAMpD"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hook for accessing network events and controls
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Uses self-managing Subscribable pattern - the network listener automatically
|
|
5
|
+
* starts when this hook mounts and stops when all subscribers unmount.
|
|
4
6
|
*/
|
|
5
7
|
import type { NetworkEvent, NetworkStats, NetworkFilter } from "../types";
|
|
6
8
|
/** Free tier limit for network requests */
|
|
@@ -9,8 +11,10 @@ export declare const FREE_TIER_REQUEST_LIMIT = 25;
|
|
|
9
11
|
* Custom hook for accessing network events and controls
|
|
10
12
|
*
|
|
11
13
|
* This hook provides a complete interface for network monitoring, including
|
|
12
|
-
* event filtering, statistics calculation, and interception control.
|
|
13
|
-
*
|
|
14
|
+
* event filtering, statistics calculation, and interception control.
|
|
15
|
+
*
|
|
16
|
+
* The network listener automatically starts when this hook mounts and stops
|
|
17
|
+
* when all components using it unmount (Subscribable pattern from TanStack Query).
|
|
14
18
|
*
|
|
15
19
|
* @returns Object containing filtered events, statistics, controls, and utilities
|
|
16
20
|
*
|
|
@@ -23,17 +27,14 @@ export declare const FREE_TIER_REQUEST_LIMIT = 25;
|
|
|
23
27
|
* filter,
|
|
24
28
|
* setFilter,
|
|
25
29
|
* clearEvents,
|
|
26
|
-
*
|
|
27
|
-
* isEnabled
|
|
30
|
+
* isCapturing
|
|
28
31
|
* } = useNetworkEvents();
|
|
29
32
|
*
|
|
30
33
|
* return (
|
|
31
34
|
* <div>
|
|
32
35
|
* <p>Total requests: {stats.totalRequests}</p>
|
|
33
36
|
* <p>Success rate: {stats.successfulRequests}/{stats.totalRequests}</p>
|
|
34
|
-
* <
|
|
35
|
-
* {isEnabled ? 'Stop' : 'Start'} Monitoring
|
|
36
|
-
* </button>
|
|
37
|
+
* <p>Capturing: {isCapturing ? 'Yes' : 'No'}</p>
|
|
37
38
|
* </div>
|
|
38
39
|
* );
|
|
39
40
|
* }
|
|
@@ -56,8 +57,8 @@ export declare function useNetworkEvents(options?: {
|
|
|
56
57
|
filter: NetworkFilter;
|
|
57
58
|
setFilter: import("react").Dispatch<import("react").SetStateAction<NetworkFilter>>;
|
|
58
59
|
clearEvents: () => void;
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
/** Whether the store has subscribers (listener is running) */
|
|
61
|
+
isCapturing: boolean;
|
|
61
62
|
hosts: string[];
|
|
62
63
|
methods: string[];
|
|
63
64
|
/** Whether there are locked events due to free tier limit */
|
|
@@ -68,5 +69,11 @@ export declare function useNetworkEvents(options?: {
|
|
|
68
69
|
isEventLocked: (eventId: string) => boolean;
|
|
69
70
|
/** Free tier request limit */
|
|
70
71
|
requestLimit: number;
|
|
72
|
+
/** Get subscriber counts for debugging */
|
|
73
|
+
getSubscriberCounts: () => {
|
|
74
|
+
eventCallbacks: number;
|
|
75
|
+
arrayListeners: number;
|
|
76
|
+
total: number;
|
|
77
|
+
};
|
|
71
78
|
};
|
|
72
79
|
//# sourceMappingURL=useNetworkEvents.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNetworkEvents.d.ts","sourceRoot":"","sources":["../../../../src/network/hooks/useNetworkEvents.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useNetworkEvents.d.ts","sourceRoot":"","sources":["../../../../src/network/hooks/useNetworkEvents.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG1E,2CAA2C;AAC3C,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO;IAwP9D,yEAAyE;;IAEzE,8CAA8C;;;;;;IAM9C,8DAA8D;;;;IAI9D,6DAA6D;;IAE7D,qDAAqD;;IAErD,kDAAkD;6BAzFR,MAAM;IA2FhD,8BAA8B;;IAE9B,0CAA0C;;;;;;EAG7C"}
|
|
@@ -7,7 +7,7 @@ export { NetworkEventItemCompact } from "./components/NetworkEventItemCompact";
|
|
|
7
7
|
export { useNetworkEvents } from "./hooks/useNetworkEvents";
|
|
8
8
|
export { TickProvider, useTickEveryMinute } from "./hooks/useTickEveryMinute";
|
|
9
9
|
export { networkListener, startNetworkListener, stopNetworkListener, addNetworkListener, removeAllNetworkListeners, isNetworkListening, getNetworkListenerCount, } from "./utils/networkListener";
|
|
10
|
-
export { networkEventStore } from "./utils/networkEventStore";
|
|
10
|
+
export { networkEventStore, type NetworkEventCallback } from "./utils/networkEventStore";
|
|
11
11
|
export { formatBytes, formatDuration, formatHttpStatus, } from "./utils/formatting";
|
|
12
12
|
export type { NetworkEvent, NetworkStats, NetworkFilter, NetworkEventStatus, NetworkInsight, } from "./types";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/network/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/network/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EACL,WAAW,EACX,cAAc,EACd,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,SAAS,CAAC"}
|
|
@@ -1,58 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Network
|
|
3
|
-
*
|
|
2
|
+
* Network Event Store
|
|
3
|
+
*
|
|
4
|
+
* Store for managing captured network requests. Uses BaseEventStore pattern -
|
|
5
|
+
* the network listener automatically starts when the first subscriber joins
|
|
6
|
+
* and stops when the last subscriber leaves.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { networkEventStore } from '@buoy-gg/network';
|
|
11
|
+
*
|
|
12
|
+
* // Subscribe to network events - automatically starts capturing!
|
|
13
|
+
* const unsubscribe = networkEventStore.subscribeToEvents((events) => {
|
|
14
|
+
* console.log('Network events:', events);
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Later, clean up - automatically stops if no other subscribers
|
|
18
|
+
* unsubscribe();
|
|
19
|
+
* ```
|
|
4
20
|
*/
|
|
21
|
+
import { BaseEventStore } from "@buoy-gg/shared-ui";
|
|
5
22
|
import type { NetworkEvent } from "../types";
|
|
6
23
|
import type { NetworkingEvent } from "./networkListener";
|
|
7
|
-
|
|
8
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Callback type for individual event notifications
|
|
26
|
+
*/
|
|
27
|
+
export type NetworkEventCallback = (event: NetworkEvent) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Callback type for full events array notifications
|
|
30
|
+
*/
|
|
31
|
+
export type NetworkEventsArrayCallback = (events: NetworkEvent[]) => void;
|
|
32
|
+
declare class NetworkEventStore extends BaseEventStore<NetworkEvent> {
|
|
9
33
|
private pendingRequests;
|
|
10
|
-
private listeners;
|
|
11
|
-
private maxEvents;
|
|
12
34
|
private recentRequests;
|
|
35
|
+
private rawListenerUnsubscribe;
|
|
36
|
+
constructor();
|
|
13
37
|
/**
|
|
14
|
-
*
|
|
38
|
+
* Start capturing network events
|
|
15
39
|
*/
|
|
16
|
-
|
|
40
|
+
protected startCapturing(): void;
|
|
17
41
|
/**
|
|
18
|
-
*
|
|
42
|
+
* Stop capturing network events
|
|
19
43
|
*/
|
|
20
|
-
|
|
44
|
+
protected stopCapturing(): void;
|
|
21
45
|
/**
|
|
22
|
-
*
|
|
46
|
+
* Check if the store is actively capturing network events
|
|
23
47
|
*/
|
|
24
|
-
|
|
48
|
+
isCapturing(): boolean;
|
|
25
49
|
/**
|
|
26
|
-
*
|
|
50
|
+
* Process a network listener event
|
|
27
51
|
*/
|
|
52
|
+
processNetworkEvent(event: NetworkingEvent): void;
|
|
53
|
+
private extractHost;
|
|
54
|
+
private extractPath;
|
|
28
55
|
private getDataSize;
|
|
29
|
-
/**
|
|
30
|
-
* Get all events
|
|
31
|
-
*/
|
|
32
|
-
getEvents(): NetworkEvent[];
|
|
33
|
-
/**
|
|
34
|
-
* Get event by ID
|
|
35
|
-
*/
|
|
36
56
|
getEventById(id: string): NetworkEvent | undefined;
|
|
37
57
|
/**
|
|
38
|
-
*
|
|
58
|
+
* Override clearEvents to also clear pending requests
|
|
39
59
|
*/
|
|
40
60
|
clearEvents(): void;
|
|
41
61
|
/**
|
|
42
|
-
* Subscribe to
|
|
43
|
-
*/
|
|
44
|
-
subscribe(listener: (events: NetworkEvent[]) => void): () => void;
|
|
45
|
-
/**
|
|
46
|
-
* Notify all listeners of changes
|
|
47
|
-
*/
|
|
48
|
-
private notifyListeners;
|
|
49
|
-
/**
|
|
50
|
-
* Set maximum number of events to store
|
|
51
|
-
*/
|
|
52
|
-
setMaxEvents(max: number): void;
|
|
53
|
-
/**
|
|
54
|
-
* Get statistics about network events
|
|
62
|
+
* Subscribe to individual events with optional replay of existing events
|
|
55
63
|
*/
|
|
64
|
+
onEvent(callback: NetworkEventCallback, replayExisting?: boolean): () => void;
|
|
56
65
|
getStats(): {
|
|
57
66
|
totalRequests: number;
|
|
58
67
|
successfulRequests: number;
|
|
@@ -62,9 +71,6 @@ declare class NetworkEventStore {
|
|
|
62
71
|
totalDataReceived: number;
|
|
63
72
|
averageDuration: number;
|
|
64
73
|
};
|
|
65
|
-
/**
|
|
66
|
-
* Filter events by criteria
|
|
67
|
-
*/
|
|
68
74
|
filterEvents(filter: {
|
|
69
75
|
method?: string;
|
|
70
76
|
status?: "success" | "error" | "pending";
|
|
@@ -73,8 +79,8 @@ declare class NetworkEventStore {
|
|
|
73
79
|
}): NetworkEvent[];
|
|
74
80
|
}
|
|
75
81
|
/**
|
|
76
|
-
* Singleton store that aggregates captured network traffic.
|
|
77
|
-
*
|
|
82
|
+
* Singleton store that aggregates captured network traffic.
|
|
83
|
+
* Automatically starts/stops capturing based on subscriber count.
|
|
78
84
|
*/
|
|
79
85
|
export declare const networkEventStore: NetworkEventStore;
|
|
80
86
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"networkEventStore.d.ts","sourceRoot":"","sources":["../../../../src/network/utils/networkEventStore.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"networkEventStore.d.ts","sourceRoot":"","sources":["../../../../src/network/utils/networkEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOzD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAE1E,cAAM,iBAAkB,SAAQ,cAAc,CAAC,YAAY,CAAC;IAC1D,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,sBAAsB,CAA6B;;IAS3D;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAgBhC;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IAc/B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAsGjD,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAUnB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIlD;;OAEG;IACH,WAAW,IAAI,IAAI;IAMnB;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,oBAAoB,EAAE,cAAc,UAAO,GAAG,MAAM,IAAI;IAkB1E,QAAQ;;;;;;;;;IAuCR,YAAY,CAAC,MAAM,EAAE;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,YAAY,EAAE;CAyCnB;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/network",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "network package",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@buoy-gg/shared-ui": "2.1.
|
|
29
|
+
"@buoy-gg/shared-ui": "2.1.3"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": "*",
|