@rozenite/network-activity-plugin 1.7.0-rc.2 → 1.8.0
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/CHANGELOG.md +31 -0
- package/README.md +24 -0
- package/dist/devtools/App.html +2 -2
- package/dist/devtools/assets/{App-pokLiGYV.js → App-B3xlUjs6.js} +163 -115
- package/dist/devtools/assets/{App-BrSkOkws.css → App-m6xge0az.css} +13 -0
- package/dist/react-native/chunks/boot-recording.cjs +307 -28
- package/dist/react-native/chunks/boot-recording.js +310 -31
- package/dist/react-native/chunks/useNetworkActivityDevTools.require.cjs +192 -141
- package/dist/react-native/chunks/useNetworkActivityDevTools.require.js +193 -142
- package/dist/react-native/index.d.ts +24 -7
- package/dist/rozenite.json +1 -1
- package/dist/sdk/index.cjs +127 -0
- package/dist/sdk/index.d.ts +1243 -0
- package/dist/sdk/index.js +127 -0
- package/package.json +17 -6
- package/sdk.ts +59 -0
- package/src/react-native/__tests__/events-listener.test.ts +35 -0
- package/src/react-native/agent/__tests__/network-activity-agent-state.test.ts +21 -9
- package/src/react-native/agent/state.ts +13 -13
- package/src/react-native/agent/tools.ts +20 -146
- package/src/react-native/agent/use-network-activity-agent-tools.ts +73 -64
- package/src/react-native/events-listener.ts +12 -3
- package/src/react-native/http/http-inspector.ts +19 -5
- package/src/react-native/network-inspector.ts +46 -8
- package/src/react-native/nitro-fetch/__tests__/nitro-network-inspector.test.ts +198 -0
- package/src/react-native/nitro-fetch/nitro-network-inspector.ts +403 -0
- package/src/react-native/useHttpInspector.ts +9 -19
- package/src/react-native/useNetworkActivityDevTools.ts +13 -1
- package/src/react-native/websocket/__tests__/websocket-inspector.test.ts +69 -0
- package/src/react-native/websocket/websocket-inspector.ts +32 -17
- package/src/shared/agent-tools.ts +230 -0
- package/src/shared/client.ts +3 -0
- package/src/shared/http-events.ts +6 -0
- package/src/shared/websocket-events.ts +16 -7
- package/src/ui/components/RequestList.tsx +21 -0
- package/src/ui/components/SidePanel.tsx +12 -9
- package/src/ui/state/derived.ts +4 -0
- package/src/ui/state/model.ts +6 -1
- package/src/ui/state/store.ts +52 -36
- package/src/ui/tabs/HeadersTab.tsx +18 -4
- package/src/ui/tabs/ResponseTab.tsx +5 -3
- package/tsconfig.json +4 -1
- package/vite.config.ts +7 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('../websocket-interceptor', () => ({
|
|
4
|
+
getWebSocketInterceptor: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
import { createWebSocketInspector } from '../websocket-inspector';
|
|
8
|
+
import type { WebSocketInterceptor } from '../websocket-interceptor';
|
|
9
|
+
|
|
10
|
+
const createInterceptor = () => {
|
|
11
|
+
const callbacks: Partial<{
|
|
12
|
+
connect: Parameters<WebSocketInterceptor['setConnectCallback']>[0];
|
|
13
|
+
open: Parameters<WebSocketInterceptor['setOnOpenCallback']>[0];
|
|
14
|
+
message: Parameters<WebSocketInterceptor['setOnMessageCallback']>[0];
|
|
15
|
+
}> = {};
|
|
16
|
+
|
|
17
|
+
const interceptor: WebSocketInterceptor = {
|
|
18
|
+
setCloseCallback: vi.fn(),
|
|
19
|
+
setSendCallback: vi.fn(),
|
|
20
|
+
setConnectCallback: vi.fn((callback) => {
|
|
21
|
+
callbacks.connect = callback;
|
|
22
|
+
}),
|
|
23
|
+
setOnOpenCallback: vi.fn((callback) => {
|
|
24
|
+
callbacks.open = callback;
|
|
25
|
+
}),
|
|
26
|
+
setOnMessageCallback: vi.fn((callback) => {
|
|
27
|
+
callbacks.message = callback;
|
|
28
|
+
}),
|
|
29
|
+
setOnErrorCallback: vi.fn(),
|
|
30
|
+
setOnCloseCallback: vi.fn(),
|
|
31
|
+
isInterceptorEnabled: vi.fn(() => true),
|
|
32
|
+
enableInterception: vi.fn(),
|
|
33
|
+
disableInterception: vi.fn(),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
callbacks,
|
|
38
|
+
interceptor,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('websocket inspector', () => {
|
|
43
|
+
it('stringifies numeric interceptor ids before emitting events', () => {
|
|
44
|
+
const { callbacks, interceptor } = createInterceptor();
|
|
45
|
+
const inspector = createWebSocketInspector(interceptor);
|
|
46
|
+
const events: Array<{ type: string; socketId: string }> = [];
|
|
47
|
+
|
|
48
|
+
inspector.on('websocket-connect', (event) => {
|
|
49
|
+
events.push({ type: event.type, socketId: event.socketId });
|
|
50
|
+
});
|
|
51
|
+
inspector.on('websocket-open', (event) => {
|
|
52
|
+
events.push({ type: event.type, socketId: event.socketId });
|
|
53
|
+
});
|
|
54
|
+
inspector.on('websocket-message-received', (event) => {
|
|
55
|
+
events.push({ type: event.type, socketId: event.socketId });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
inspector.enable();
|
|
59
|
+
callbacks.connect?.('wss://example.com/socket', ['chat'], [], 42);
|
|
60
|
+
callbacks.open?.(42);
|
|
61
|
+
callbacks.message?.('hello', 42);
|
|
62
|
+
|
|
63
|
+
expect(events).toEqual([
|
|
64
|
+
{ type: 'websocket-connect', socketId: '42' },
|
|
65
|
+
{ type: 'websocket-open', socketId: '42' },
|
|
66
|
+
{ type: 'websocket-message-received', socketId: '42' },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
WebSocketEventMap,
|
|
6
6
|
} from '../../shared/websocket-events';
|
|
7
7
|
import type { Inspector } from '../inspector';
|
|
8
|
+
import type { WebSocketInterceptor } from './websocket-interceptor';
|
|
8
9
|
|
|
9
10
|
type NanoEventsMap = {
|
|
10
11
|
[K in keyof WebSocketEventMap]: (data: WebSocketEventMap[K]) => void;
|
|
@@ -23,15 +24,18 @@ export const WEBSOCKET_EVENTS: (keyof WebSocketEventMap)[] = [
|
|
|
23
24
|
];
|
|
24
25
|
|
|
25
26
|
export const isWebSocketEvent = (
|
|
26
|
-
type: string
|
|
27
|
+
type: string,
|
|
27
28
|
): type is keyof WebSocketEventMap => {
|
|
28
29
|
return (WEBSOCKET_EVENTS as readonly string[]).includes(type);
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
const toSocketId = (socketId: number) => String(socketId);
|
|
33
|
+
|
|
34
|
+
export const createWebSocketInspector = (
|
|
35
|
+
webSocketInterceptor: WebSocketInterceptor = getWebSocketInterceptor(),
|
|
36
|
+
): WebSocketInspector => {
|
|
32
37
|
const eventEmitter = createNanoEvents<NanoEventsMap>();
|
|
33
38
|
const socketUrlMap = new Map<number, string>();
|
|
34
|
-
const webSocketInterceptor = getWebSocketInterceptor();
|
|
35
39
|
|
|
36
40
|
return {
|
|
37
41
|
enable: () => {
|
|
@@ -40,19 +44,20 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
40
44
|
url: string,
|
|
41
45
|
protocols: string[] | null,
|
|
42
46
|
options: string[],
|
|
43
|
-
socketId: number
|
|
47
|
+
socketId: number,
|
|
44
48
|
) => {
|
|
45
49
|
socketUrlMap.set(socketId, url);
|
|
46
50
|
const event: WebSocketEvent = {
|
|
47
51
|
type: 'websocket-connect',
|
|
48
52
|
url,
|
|
49
|
-
socketId,
|
|
53
|
+
socketId: toSocketId(socketId),
|
|
50
54
|
timestamp: Date.now(),
|
|
51
55
|
protocols,
|
|
52
56
|
options,
|
|
57
|
+
source: 'builtin',
|
|
53
58
|
};
|
|
54
59
|
eventEmitter.emit('websocket-connect', event);
|
|
55
|
-
}
|
|
60
|
+
},
|
|
56
61
|
);
|
|
57
62
|
|
|
58
63
|
webSocketInterceptor.setCloseCallback(
|
|
@@ -66,14 +71,15 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
66
71
|
const event: WebSocketEvent = {
|
|
67
72
|
type: 'websocket-close',
|
|
68
73
|
url,
|
|
69
|
-
socketId,
|
|
74
|
+
socketId: toSocketId(socketId),
|
|
70
75
|
timestamp: Date.now(),
|
|
71
76
|
code: code || 0,
|
|
72
77
|
reason: reason || undefined,
|
|
78
|
+
source: 'builtin',
|
|
73
79
|
};
|
|
74
80
|
eventEmitter.emit('websocket-close', event);
|
|
75
81
|
socketUrlMap.delete(socketId);
|
|
76
|
-
}
|
|
82
|
+
},
|
|
77
83
|
);
|
|
78
84
|
|
|
79
85
|
webSocketInterceptor.setOnMessageCallback(
|
|
@@ -87,13 +93,14 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
87
93
|
const event: WebSocketEvent = {
|
|
88
94
|
type: 'websocket-message-received',
|
|
89
95
|
url,
|
|
90
|
-
socketId,
|
|
96
|
+
socketId: toSocketId(socketId),
|
|
91
97
|
timestamp: Date.now(),
|
|
92
98
|
data,
|
|
93
99
|
messageType: typeof data === 'string' ? 'text' : 'binary',
|
|
100
|
+
source: 'builtin',
|
|
94
101
|
};
|
|
95
102
|
eventEmitter.emit('websocket-message-received', event);
|
|
96
|
-
}
|
|
103
|
+
},
|
|
97
104
|
);
|
|
98
105
|
|
|
99
106
|
webSocketInterceptor.setOnErrorCallback(
|
|
@@ -107,12 +114,13 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
107
114
|
const event: WebSocketEvent = {
|
|
108
115
|
type: 'websocket-error',
|
|
109
116
|
url,
|
|
110
|
-
socketId,
|
|
117
|
+
socketId: toSocketId(socketId),
|
|
111
118
|
timestamp: Date.now(),
|
|
112
119
|
error,
|
|
120
|
+
source: 'builtin',
|
|
113
121
|
};
|
|
114
122
|
eventEmitter.emit('websocket-error', event);
|
|
115
|
-
}
|
|
123
|
+
},
|
|
116
124
|
);
|
|
117
125
|
|
|
118
126
|
webSocketInterceptor.setSendCallback((data: string, socketId: number) => {
|
|
@@ -125,10 +133,11 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
125
133
|
const event: WebSocketEvent = {
|
|
126
134
|
type: 'websocket-message-sent',
|
|
127
135
|
url,
|
|
128
|
-
socketId,
|
|
136
|
+
socketId: toSocketId(socketId),
|
|
129
137
|
timestamp: Date.now(),
|
|
130
138
|
data,
|
|
131
139
|
messageType: typeof data === 'string' ? 'text' : 'binary',
|
|
140
|
+
source: 'builtin',
|
|
132
141
|
};
|
|
133
142
|
eventEmitter.emit('websocket-message-sent', event);
|
|
134
143
|
});
|
|
@@ -143,8 +152,9 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
143
152
|
const event: WebSocketEvent = {
|
|
144
153
|
type: 'websocket-open',
|
|
145
154
|
url,
|
|
146
|
-
socketId,
|
|
155
|
+
socketId: toSocketId(socketId),
|
|
147
156
|
timestamp: Date.now(),
|
|
157
|
+
source: 'builtin',
|
|
148
158
|
};
|
|
149
159
|
eventEmitter.emit('websocket-open', event);
|
|
150
160
|
});
|
|
@@ -160,14 +170,15 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
160
170
|
const event: WebSocketEvent = {
|
|
161
171
|
type: 'websocket-close',
|
|
162
172
|
url,
|
|
163
|
-
socketId,
|
|
173
|
+
socketId: toSocketId(socketId),
|
|
164
174
|
timestamp: Date.now(),
|
|
165
175
|
code: error.code,
|
|
166
176
|
reason: error.reason,
|
|
177
|
+
source: 'builtin',
|
|
167
178
|
};
|
|
168
179
|
eventEmitter.emit('websocket-close', event);
|
|
169
180
|
socketUrlMap.delete(socketId);
|
|
170
|
-
}
|
|
181
|
+
},
|
|
171
182
|
);
|
|
172
183
|
|
|
173
184
|
webSocketInterceptor.enableInterception();
|
|
@@ -182,7 +193,11 @@ export const getWebSocketInspector = (): WebSocketInspector => {
|
|
|
182
193
|
},
|
|
183
194
|
on: <TEventType extends keyof WebSocketEventMap>(
|
|
184
195
|
event: TEventType,
|
|
185
|
-
callback: (data: WebSocketEventMap[TEventType]) => void
|
|
196
|
+
callback: (data: WebSocketEventMap[TEventType]) => void,
|
|
186
197
|
) => eventEmitter.on(event, callback as NanoEventsMap[TEventType]),
|
|
187
198
|
};
|
|
188
199
|
};
|
|
200
|
+
|
|
201
|
+
export const getWebSocketInspector = (): WebSocketInspector => {
|
|
202
|
+
return createWebSocketInspector();
|
|
203
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineAgentToolContract,
|
|
3
|
+
type AgentToolContract,
|
|
4
|
+
} from '@rozenite/agent-shared';
|
|
5
|
+
import type {
|
|
6
|
+
NetworkActivityAgentBodyResult,
|
|
7
|
+
NetworkActivityAgentState,
|
|
8
|
+
} from '../react-native/agent/state';
|
|
9
|
+
|
|
10
|
+
export const NETWORK_ACTIVITY_AGENT_PLUGIN_ID =
|
|
11
|
+
'@rozenite/network-activity-plugin';
|
|
12
|
+
|
|
13
|
+
export type NetworkActivityPaginationArgs = {
|
|
14
|
+
limit?: number;
|
|
15
|
+
cursor?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type NetworkActivityRequestIdArgs = {
|
|
19
|
+
requestId: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type NetworkActivityStartRecordingArgs = undefined;
|
|
23
|
+
|
|
24
|
+
export type NetworkActivityGetRecordingStatusArgs = undefined;
|
|
25
|
+
|
|
26
|
+
export type NetworkActivityGetRecordingStatusResult = ReturnType<
|
|
27
|
+
NetworkActivityAgentState['getStatus']
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export type NetworkActivityStartRecordingResult =
|
|
31
|
+
NetworkActivityGetRecordingStatusResult & {
|
|
32
|
+
started: true;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type NetworkActivityStopRecordingArgs = undefined;
|
|
36
|
+
|
|
37
|
+
export type NetworkActivityStopRecordingResult =
|
|
38
|
+
NetworkActivityGetRecordingStatusResult & {
|
|
39
|
+
stopped: true;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type NetworkActivityListRequestsArgs = NetworkActivityPaginationArgs;
|
|
43
|
+
|
|
44
|
+
export type NetworkActivityListRequestsResult = ReturnType<
|
|
45
|
+
NetworkActivityAgentState['listRequests']
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
export type NetworkActivityGetRequestDetailsArgs = NetworkActivityRequestIdArgs;
|
|
49
|
+
|
|
50
|
+
export type NetworkActivityGetRequestDetailsResult = ReturnType<
|
|
51
|
+
NetworkActivityAgentState['getRequestDetails']
|
|
52
|
+
>;
|
|
53
|
+
|
|
54
|
+
export type NetworkActivityGetRequestBodyArgs = NetworkActivityRequestIdArgs;
|
|
55
|
+
|
|
56
|
+
export type NetworkActivityGetRequestBodyResult = NetworkActivityAgentBodyResult;
|
|
57
|
+
|
|
58
|
+
export type NetworkActivityGetResponseBodyArgs = NetworkActivityRequestIdArgs;
|
|
59
|
+
|
|
60
|
+
export type NetworkActivityGetResponseBodyResult = NetworkActivityAgentBodyResult;
|
|
61
|
+
|
|
62
|
+
export type NetworkActivityListRealtimeConnectionsArgs =
|
|
63
|
+
NetworkActivityPaginationArgs;
|
|
64
|
+
|
|
65
|
+
export type NetworkActivityListRealtimeConnectionsResult = ReturnType<
|
|
66
|
+
NetworkActivityAgentState['listRealtimeConnections']
|
|
67
|
+
>;
|
|
68
|
+
|
|
69
|
+
export type NetworkActivityGetRealtimeConnectionDetailsArgs =
|
|
70
|
+
NetworkActivityRequestIdArgs;
|
|
71
|
+
|
|
72
|
+
export type NetworkActivityGetRealtimeConnectionDetailsResult = ReturnType<
|
|
73
|
+
NetworkActivityAgentState['getRealtimeConnectionDetails']
|
|
74
|
+
>;
|
|
75
|
+
|
|
76
|
+
export const networkActivityToolDefinitions = {
|
|
77
|
+
startRecording: defineAgentToolContract<
|
|
78
|
+
NetworkActivityStartRecordingArgs,
|
|
79
|
+
NetworkActivityStartRecordingResult
|
|
80
|
+
>({
|
|
81
|
+
name: 'startRecording',
|
|
82
|
+
description:
|
|
83
|
+
'Start recording network activity in the fallback network activity plugin.',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {},
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
stopRecording: defineAgentToolContract<
|
|
90
|
+
NetworkActivityStopRecordingArgs,
|
|
91
|
+
NetworkActivityStopRecordingResult
|
|
92
|
+
>({
|
|
93
|
+
name: 'stopRecording',
|
|
94
|
+
description:
|
|
95
|
+
'Stop recording network activity without clearing the captured plugin buffer.',
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {},
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
getRecordingStatus: defineAgentToolContract<
|
|
102
|
+
NetworkActivityGetRecordingStatusArgs,
|
|
103
|
+
NetworkActivityGetRecordingStatusResult
|
|
104
|
+
>({
|
|
105
|
+
name: 'getRecordingStatus',
|
|
106
|
+
description:
|
|
107
|
+
'Return network activity plugin recording state and buffer metadata.',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {},
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
listRequests: defineAgentToolContract<
|
|
114
|
+
NetworkActivityListRequestsArgs,
|
|
115
|
+
NetworkActivityListRequestsResult
|
|
116
|
+
>({
|
|
117
|
+
name: 'listRequests',
|
|
118
|
+
description:
|
|
119
|
+
'List captured HTTP request summaries with cursor pagination from the fallback plugin.',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
limit: {
|
|
124
|
+
type: 'number',
|
|
125
|
+
description: 'Maximum number of requests to return. Defaults to 20.',
|
|
126
|
+
},
|
|
127
|
+
cursor: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
description:
|
|
130
|
+
'Opaque pagination cursor from a previous listRequests call.',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
getRequestDetails: defineAgentToolContract<
|
|
136
|
+
NetworkActivityGetRequestDetailsArgs,
|
|
137
|
+
NetworkActivityGetRequestDetailsResult
|
|
138
|
+
>({
|
|
139
|
+
name: 'getRequestDetails',
|
|
140
|
+
description:
|
|
141
|
+
'Return detailed metadata for a captured HTTP request without fetching response body.',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
requestId: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: 'Captured plugin request ID to inspect.',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
required: ['requestId'],
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
getRequestBody: defineAgentToolContract<
|
|
154
|
+
NetworkActivityGetRequestBodyArgs,
|
|
155
|
+
NetworkActivityGetRequestBodyResult
|
|
156
|
+
>({
|
|
157
|
+
name: 'getRequestBody',
|
|
158
|
+
description:
|
|
159
|
+
'Return the captured request body for a plugin-recorded HTTP request when available.',
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
requestId: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
description: 'Captured plugin request ID to inspect.',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
required: ['requestId'],
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
getResponseBody: defineAgentToolContract<
|
|
172
|
+
NetworkActivityGetResponseBodyArgs,
|
|
173
|
+
NetworkActivityGetResponseBodyResult
|
|
174
|
+
>({
|
|
175
|
+
name: 'getResponseBody',
|
|
176
|
+
description:
|
|
177
|
+
'Return the captured response body for a plugin-recorded HTTP request when available.',
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {
|
|
181
|
+
requestId: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
description: 'Captured plugin request ID to inspect.',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
required: ['requestId'],
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
listRealtimeConnections: defineAgentToolContract<
|
|
190
|
+
NetworkActivityListRealtimeConnectionsArgs,
|
|
191
|
+
NetworkActivityListRealtimeConnectionsResult
|
|
192
|
+
>({
|
|
193
|
+
name: 'listRealtimeConnections',
|
|
194
|
+
description:
|
|
195
|
+
'List captured WebSocket and SSE connections with cursor pagination.',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
limit: {
|
|
200
|
+
type: 'number',
|
|
201
|
+
description:
|
|
202
|
+
'Maximum number of realtime connections to return. Defaults to 20.',
|
|
203
|
+
},
|
|
204
|
+
cursor: {
|
|
205
|
+
type: 'string',
|
|
206
|
+
description:
|
|
207
|
+
'Opaque pagination cursor from a previous listRealtimeConnections call.',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
getRealtimeConnectionDetails: defineAgentToolContract<
|
|
213
|
+
NetworkActivityGetRealtimeConnectionDetailsArgs,
|
|
214
|
+
NetworkActivityGetRealtimeConnectionDetailsResult
|
|
215
|
+
>({
|
|
216
|
+
name: 'getRealtimeConnectionDetails',
|
|
217
|
+
description:
|
|
218
|
+
'Return details for a captured WebSocket or SSE connection, including recent messages.',
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
requestId: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Captured realtime request ID to inspect.',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
required: ['requestId'],
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
230
|
+
} as const satisfies Record<string, AgentToolContract<unknown, unknown>>;
|
package/src/shared/client.ts
CHANGED
|
@@ -14,6 +14,9 @@ export type NetworkActivityEventMap = {
|
|
|
14
14
|
'network-enable': unknown;
|
|
15
15
|
'network-disable': unknown;
|
|
16
16
|
|
|
17
|
+
// Recording state sync (sent by React Native to inform DevTools UI of current state)
|
|
18
|
+
'recording-state': { isRecording: boolean };
|
|
19
|
+
|
|
17
20
|
// Client UI settings events
|
|
18
21
|
'get-client-ui-settings': unknown;
|
|
19
22
|
'client-ui-settings': {
|
|
@@ -5,6 +5,7 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
|
|
|
5
5
|
|
|
6
6
|
export type RequestId = string;
|
|
7
7
|
export type Timestamp = number;
|
|
8
|
+
export type NetworkEventSource = 'builtin' | 'nitro';
|
|
8
9
|
|
|
9
10
|
export type XHRPostData =
|
|
10
11
|
| string
|
|
@@ -92,6 +93,7 @@ export type HttpEventMap = {
|
|
|
92
93
|
timestamp: Timestamp;
|
|
93
94
|
initiator: Initiator;
|
|
94
95
|
type: ResourceType;
|
|
96
|
+
source?: NetworkEventSource;
|
|
95
97
|
};
|
|
96
98
|
|
|
97
99
|
'response-received': {
|
|
@@ -99,6 +101,7 @@ export type HttpEventMap = {
|
|
|
99
101
|
timestamp: Timestamp;
|
|
100
102
|
type: ResourceType;
|
|
101
103
|
response: Response;
|
|
104
|
+
source?: NetworkEventSource;
|
|
102
105
|
};
|
|
103
106
|
|
|
104
107
|
'request-completed': {
|
|
@@ -107,6 +110,7 @@ export type HttpEventMap = {
|
|
|
107
110
|
duration: number;
|
|
108
111
|
size: number | null;
|
|
109
112
|
ttfb: number;
|
|
113
|
+
source?: NetworkEventSource;
|
|
110
114
|
};
|
|
111
115
|
|
|
112
116
|
'request-failed': {
|
|
@@ -115,6 +119,7 @@ export type HttpEventMap = {
|
|
|
115
119
|
type: ResourceType;
|
|
116
120
|
error: string;
|
|
117
121
|
canceled: boolean;
|
|
122
|
+
source?: NetworkEventSource;
|
|
118
123
|
};
|
|
119
124
|
|
|
120
125
|
'request-progress': {
|
|
@@ -123,6 +128,7 @@ export type HttpEventMap = {
|
|
|
123
128
|
loaded: number;
|
|
124
129
|
total: number;
|
|
125
130
|
lengthComputable: boolean;
|
|
131
|
+
source?: NetworkEventSource;
|
|
126
132
|
};
|
|
127
133
|
|
|
128
134
|
'get-response-body': {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { NetworkEventSource } from './http-events';
|
|
2
|
+
|
|
1
3
|
export type WebSocketMessageType = 'text' | 'binary';
|
|
2
4
|
|
|
3
5
|
export type WebSocketConnectionStatus =
|
|
@@ -9,60 +11,67 @@ export type WebSocketConnectionStatus =
|
|
|
9
11
|
export type WebSocketConnectEvent = {
|
|
10
12
|
type: 'websocket-connect';
|
|
11
13
|
url: string;
|
|
12
|
-
socketId:
|
|
14
|
+
socketId: string;
|
|
13
15
|
timestamp: number;
|
|
14
16
|
protocols: string[] | null;
|
|
15
17
|
options: string[];
|
|
18
|
+
source?: NetworkEventSource;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
21
|
export type WebSocketOpenEvent = {
|
|
19
22
|
type: 'websocket-open';
|
|
20
23
|
url: string;
|
|
21
|
-
socketId:
|
|
24
|
+
socketId: string;
|
|
22
25
|
timestamp: number;
|
|
26
|
+
source?: NetworkEventSource;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
export type WebSocketCloseEvent = {
|
|
26
30
|
type: 'websocket-close';
|
|
27
31
|
url: string;
|
|
28
|
-
socketId:
|
|
32
|
+
socketId: string;
|
|
29
33
|
timestamp: number;
|
|
30
34
|
code: number;
|
|
31
35
|
reason?: string;
|
|
36
|
+
source?: NetworkEventSource;
|
|
32
37
|
};
|
|
33
38
|
|
|
34
39
|
export type WebSocketMessageSentEvent = {
|
|
35
40
|
type: 'websocket-message-sent';
|
|
36
41
|
url: string;
|
|
37
|
-
socketId:
|
|
42
|
+
socketId: string;
|
|
38
43
|
timestamp: number;
|
|
39
44
|
data: string;
|
|
40
45
|
messageType: WebSocketMessageType;
|
|
46
|
+
source?: NetworkEventSource;
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
export type WebSocketMessageReceivedEvent = {
|
|
44
50
|
type: 'websocket-message-received';
|
|
45
51
|
url: string;
|
|
46
|
-
socketId:
|
|
52
|
+
socketId: string;
|
|
47
53
|
timestamp: number;
|
|
48
54
|
data: string;
|
|
49
55
|
messageType: WebSocketMessageType;
|
|
56
|
+
source?: NetworkEventSource;
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
export type WebSocketErrorEvent = {
|
|
53
60
|
type: 'websocket-error';
|
|
54
61
|
url: string;
|
|
55
|
-
socketId:
|
|
62
|
+
socketId: string;
|
|
56
63
|
timestamp: number;
|
|
57
64
|
error: string;
|
|
65
|
+
source?: NetworkEventSource;
|
|
58
66
|
};
|
|
59
67
|
|
|
60
68
|
export type WebSocketConnectionStatusChangedEvent = {
|
|
61
69
|
type: 'websocket-connection-status-changed';
|
|
62
70
|
url: string;
|
|
63
|
-
socketId:
|
|
71
|
+
socketId: string;
|
|
64
72
|
timestamp: number;
|
|
65
73
|
status: WebSocketConnectionStatus;
|
|
74
|
+
source?: NetworkEventSource;
|
|
66
75
|
};
|
|
67
76
|
|
|
68
77
|
export type WebSocketEvent =
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { getStatusColor } from '../utils/getStatusColor';
|
|
21
21
|
import { FilterState } from './FilterBar';
|
|
22
22
|
import { isNumber } from '../../utils/typeChecks';
|
|
23
|
+
import type { NetworkEventSource } from '../../shared/client';
|
|
23
24
|
|
|
24
25
|
type NetworkRequest = {
|
|
25
26
|
id: RequestId;
|
|
@@ -31,10 +32,23 @@ type NetworkRequest = {
|
|
|
31
32
|
size: string;
|
|
32
33
|
time: string;
|
|
33
34
|
type: string;
|
|
35
|
+
source?: NetworkEventSource;
|
|
34
36
|
startTime: string;
|
|
35
37
|
hasOverride: boolean;
|
|
36
38
|
};
|
|
37
39
|
|
|
40
|
+
const getSourceLabel = (source?: NetworkEventSource) => {
|
|
41
|
+
if (source === 'nitro') {
|
|
42
|
+
return 'Nitro';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (source === 'builtin') {
|
|
46
|
+
return 'Built-in';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
|
|
38
52
|
const formatSize = (bytes: number): string => {
|
|
39
53
|
if (bytes === 0) return '0 B';
|
|
40
54
|
const k = 1024;
|
|
@@ -153,6 +167,7 @@ const processNetworkRequests = (
|
|
|
153
167
|
size: isNumber(request.size) ? formatSize(request.size) : '—',
|
|
154
168
|
time: formatDuration(duration),
|
|
155
169
|
type: request.type,
|
|
170
|
+
source: request.source,
|
|
156
171
|
startTime: formatStartTime(request.timestamp),
|
|
157
172
|
hasOverride: hasOverride,
|
|
158
173
|
};
|
|
@@ -177,6 +192,12 @@ const columns = [
|
|
|
177
192
|
{row.original.hasOverride && (
|
|
178
193
|
<span className="w-2 h-2 rounded-full bg-violet-300 ms-2 inline-block"></span>
|
|
179
194
|
)}
|
|
195
|
+
|
|
196
|
+
{getSourceLabel(row.original.source) && (
|
|
197
|
+
<span className="ml-2 rounded border border-gray-700 px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-gray-400">
|
|
198
|
+
{getSourceLabel(row.original.source)}
|
|
199
|
+
</span>
|
|
200
|
+
)}
|
|
180
201
|
</div>
|
|
181
202
|
),
|
|
182
203
|
sortingFn: 'alphanumeric',
|