@rozenite/network-activity-plugin 1.5.0 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Network Activity for Rozenite.",
5
5
  "type": "module",
6
6
  "main": "./dist/react-native.cjs",
@@ -8,7 +8,8 @@
8
8
  "types": "./dist/react-native.d.ts",
9
9
  "dependencies": {
10
10
  "nanoevents": "^9.1.0",
11
- "@rozenite/plugin-bridge": "1.5.0"
11
+ "@rozenite/agent-bridge": "1.6.0",
12
+ "@rozenite/plugin-bridge": "1.6.0"
12
13
  },
13
14
  "devDependencies": {
14
15
  "@floating-ui/react": "^0.26.0",
@@ -39,8 +40,8 @@
39
40
  "typescript": "~5.9.3",
40
41
  "vite": "^7.3.1",
41
42
  "zustand": "^5.0.6",
42
- "rozenite": "1.5.0",
43
- "@rozenite/vite-plugin": "1.5.0"
43
+ "@rozenite/vite-plugin": "1.6.0",
44
+ "rozenite": "1.6.0"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "react-native-sse": "*"
@@ -0,0 +1,250 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createNetworkActivityAgentState } from '../state';
3
+ import { NETWORK_ACTIVITY_AGENT_TOOLS } from '../tools';
4
+ import type { Request } from '../../../shared/client';
5
+
6
+ const createRequest = (overrides?: Partial<Request>): Request => ({
7
+ url: 'https://example.com/api',
8
+ method: 'POST',
9
+ headers: {
10
+ 'content-type': 'application/json',
11
+ },
12
+ postData: {
13
+ type: 'text',
14
+ value: '{"hello":"world"}',
15
+ },
16
+ ...overrides,
17
+ });
18
+
19
+ describe('network activity agent state', () => {
20
+ it('exposes the expected fallback and realtime tool names', () => {
21
+ expect(NETWORK_ACTIVITY_AGENT_TOOLS.map((tool) => tool.name)).toEqual([
22
+ 'startRecording',
23
+ 'stopRecording',
24
+ 'getRecordingStatus',
25
+ 'listRequests',
26
+ 'getRequestDetails',
27
+ 'getRequestBody',
28
+ 'getResponseBody',
29
+ 'listRealtimeConnections',
30
+ 'getRealtimeConnectionDetails',
31
+ ]);
32
+ });
33
+
34
+ it('tracks HTTP requests with parity-oriented list/detail/body results', () => {
35
+ const state = createNetworkActivityAgentState();
36
+ state.startRecording();
37
+
38
+ state.onRequestSent({
39
+ requestId: 'req-1',
40
+ timestamp: 100,
41
+ request: createRequest(),
42
+ type: 'XHR',
43
+ initiator: { type: 'script', url: 'App.tsx', lineNumber: 12, columnNumber: 4 },
44
+ });
45
+ state.onResponseReceived({
46
+ requestId: 'req-1',
47
+ timestamp: 120,
48
+ type: 'XHR',
49
+ response: {
50
+ url: 'https://example.com/api',
51
+ status: 200,
52
+ statusText: 'OK',
53
+ headers: { 'content-type': 'application/json' },
54
+ contentType: 'application/json',
55
+ size: 17,
56
+ responseTime: 120,
57
+ },
58
+ });
59
+ state.onRequestCompleted({
60
+ requestId: 'req-1',
61
+ timestamp: 130,
62
+ duration: 30,
63
+ size: 17,
64
+ ttfb: 20,
65
+ });
66
+
67
+ const list = state.listRequests({});
68
+ expect(list.items).toEqual([
69
+ {
70
+ requestId: 'req-1',
71
+ method: 'POST',
72
+ url: 'https://example.com/api',
73
+ status: 200,
74
+ type: 'XHR',
75
+ startTimeMs: 100,
76
+ endTimeMs: 130,
77
+ durationMs: 30,
78
+ transferSize: 17,
79
+ encodedDataLength: 17,
80
+ outcome: 'success',
81
+ },
82
+ ]);
83
+
84
+ const details = state.getRequestDetails('req-1');
85
+ expect(details.request).toMatchObject({
86
+ requestId: 'req-1',
87
+ method: 'POST',
88
+ url: 'https://example.com/api',
89
+ type: 'XHR',
90
+ loadingFinished: true,
91
+ loadingFailed: false,
92
+ ttfb: 20,
93
+ size: 17,
94
+ });
95
+
96
+ expect(state.getRequestBody('req-1')).toEqual({
97
+ requestId: 'req-1',
98
+ available: true,
99
+ body: '{"hello":"world"}',
100
+ base64Encoded: false,
101
+ });
102
+ });
103
+
104
+ it('returns unavailable request bodies when none were captured', () => {
105
+ const state = createNetworkActivityAgentState();
106
+ state.startRecording();
107
+ state.onRequestSent({
108
+ requestId: 'req-2',
109
+ timestamp: 10,
110
+ request: createRequest({ postData: undefined, method: 'GET' }),
111
+ type: 'Fetch',
112
+ initiator: { type: 'other' },
113
+ });
114
+
115
+ expect(state.getRequestBody('req-2')).toEqual({
116
+ requestId: 'req-2',
117
+ available: false,
118
+ reason: 'No request body is available for this request.',
119
+ });
120
+ });
121
+
122
+ it('supports realtime listing and details for websocket and sse traffic', () => {
123
+ const state = createNetworkActivityAgentState();
124
+ state.startRecording();
125
+
126
+ state.onWebSocketConnect({
127
+ type: 'websocket-connect',
128
+ url: 'wss://example.com/socket',
129
+ socketId: 7,
130
+ timestamp: 100,
131
+ protocols: ['chat'],
132
+ options: [],
133
+ });
134
+ state.onWebSocketOpen({
135
+ type: 'websocket-open',
136
+ url: 'wss://example.com/socket',
137
+ socketId: 7,
138
+ timestamp: 110,
139
+ });
140
+ state.onWebSocketMessageSent({
141
+ type: 'websocket-message-sent',
142
+ url: 'wss://example.com/socket',
143
+ socketId: 7,
144
+ timestamp: 120,
145
+ data: 'ping',
146
+ messageType: 'text',
147
+ });
148
+ state.onWebSocketMessageReceived({
149
+ type: 'websocket-message-received',
150
+ url: 'wss://example.com/socket',
151
+ socketId: 7,
152
+ timestamp: 121,
153
+ data: 'pong',
154
+ messageType: 'text',
155
+ });
156
+
157
+ state.onRequestSent({
158
+ requestId: 'req-sse',
159
+ timestamp: 200,
160
+ request: createRequest({
161
+ method: 'GET',
162
+ url: 'https://example.com/stream',
163
+ postData: undefined,
164
+ }),
165
+ type: 'Fetch',
166
+ initiator: { type: 'script' },
167
+ });
168
+ state.onSSEOpen({
169
+ type: 'sse-open',
170
+ requestId: 'req-sse',
171
+ timestamp: 210,
172
+ response: {
173
+ url: 'https://example.com/stream',
174
+ status: 200,
175
+ statusText: 'OK',
176
+ headers: { 'content-type': 'text/event-stream' },
177
+ contentType: 'text/event-stream',
178
+ size: 0,
179
+ responseTime: 210,
180
+ },
181
+ });
182
+ state.onSSEMessage({
183
+ type: 'sse-message',
184
+ requestId: 'req-sse',
185
+ timestamp: 220,
186
+ payload: {
187
+ type: 'message',
188
+ data: 'hello',
189
+ },
190
+ });
191
+
192
+ const list = state.listRealtimeConnections({});
193
+ expect(list.items).toHaveLength(2);
194
+ expect(list.items[0]).toMatchObject({
195
+ requestId: 'req-sse',
196
+ kind: 'sse',
197
+ status: 'open',
198
+ messageCount: 1,
199
+ });
200
+ expect(list.items[1]).toMatchObject({
201
+ requestId: 'ws-7',
202
+ kind: 'websocket',
203
+ status: 'open',
204
+ messageCount: 2,
205
+ });
206
+
207
+ const websocketDetails = state.getRealtimeConnectionDetails('ws-7');
208
+ expect(websocketDetails.connection).toMatchObject({
209
+ requestId: 'ws-7',
210
+ kind: 'websocket',
211
+ url: 'wss://example.com/socket',
212
+ status: 'open',
213
+ });
214
+ expect(websocketDetails.connection.messages).toHaveLength(2);
215
+
216
+ const sseDetails = state.getRealtimeConnectionDetails('req-sse');
217
+ expect(sseDetails.connection).toMatchObject({
218
+ requestId: 'req-sse',
219
+ kind: 'sse',
220
+ status: 'open',
221
+ });
222
+ expect(sseDetails.connection.messages).toHaveLength(1);
223
+ });
224
+
225
+ it('invalidates old cursors after a new recording starts', () => {
226
+ const state = createNetworkActivityAgentState();
227
+ state.startRecording();
228
+ state.onRequestSent({
229
+ requestId: 'req-1',
230
+ timestamp: 100,
231
+ request: createRequest(),
232
+ type: 'XHR',
233
+ initiator: { type: 'other' },
234
+ });
235
+ state.onRequestSent({
236
+ requestId: 'req-2',
237
+ timestamp: 101,
238
+ request: createRequest({ url: 'https://example.com/api/2' }),
239
+ type: 'XHR',
240
+ initiator: { type: 'other' },
241
+ });
242
+
243
+ const firstPage = state.listRequests({ limit: 1 });
244
+ state.startRecording();
245
+
246
+ expect(() =>
247
+ state.listRequests({ limit: 1, cursor: firstPage.page.nextCursor })
248
+ ).toThrow('Cursor does not match the requested listing. Run the command again.');
249
+ });
250
+ });