@buoy-gg/network 1.7.2

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.
Files changed (67) hide show
  1. package/README.md +381 -0
  2. package/lib/commonjs/index.js +34 -0
  3. package/lib/commonjs/network/components/NetworkCopySettingsView.js +867 -0
  4. package/lib/commonjs/network/components/NetworkEventDetailView.js +837 -0
  5. package/lib/commonjs/network/components/NetworkEventItemCompact.js +323 -0
  6. package/lib/commonjs/network/components/NetworkFilterViewV3.js +297 -0
  7. package/lib/commonjs/network/components/NetworkModal.js +937 -0
  8. package/lib/commonjs/network/hooks/useNetworkEvents.js +320 -0
  9. package/lib/commonjs/network/hooks/useTickEveryMinute.js +34 -0
  10. package/lib/commonjs/network/index.js +102 -0
  11. package/lib/commonjs/network/types/index.js +1 -0
  12. package/lib/commonjs/network/utils/extractOperationName.js +80 -0
  13. package/lib/commonjs/network/utils/formatGraphQLVariables.js +219 -0
  14. package/lib/commonjs/network/utils/formatting.js +30 -0
  15. package/lib/commonjs/network/utils/networkEventStore.js +269 -0
  16. package/lib/commonjs/network/utils/networkListener.js +801 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/preset.js +83 -0
  19. package/lib/module/index.js +7 -0
  20. package/lib/module/network/components/NetworkCopySettingsView.js +862 -0
  21. package/lib/module/network/components/NetworkEventDetailView.js +834 -0
  22. package/lib/module/network/components/NetworkEventItemCompact.js +320 -0
  23. package/lib/module/network/components/NetworkFilterViewV3.js +293 -0
  24. package/lib/module/network/components/NetworkModal.js +933 -0
  25. package/lib/module/network/hooks/useNetworkEvents.js +316 -0
  26. package/lib/module/network/hooks/useTickEveryMinute.js +29 -0
  27. package/lib/module/network/index.js +20 -0
  28. package/lib/module/network/types/index.js +1 -0
  29. package/lib/module/network/utils/extractOperationName.js +76 -0
  30. package/lib/module/network/utils/formatGraphQLVariables.js +213 -0
  31. package/lib/module/network/utils/formatting.js +9 -0
  32. package/lib/module/network/utils/networkEventStore.js +265 -0
  33. package/lib/module/network/utils/networkListener.js +791 -0
  34. package/lib/module/preset.js +79 -0
  35. package/lib/typescript/index.d.ts +3 -0
  36. package/lib/typescript/index.d.ts.map +1 -0
  37. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts +26 -0
  38. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +1 -0
  39. package/lib/typescript/network/components/NetworkEventDetailView.d.ts +13 -0
  40. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -0
  41. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +12 -0
  42. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -0
  43. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts +22 -0
  44. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +1 -0
  45. package/lib/typescript/network/components/NetworkModal.d.ts +14 -0
  46. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -0
  47. package/lib/typescript/network/hooks/useNetworkEvents.d.ts +72 -0
  48. package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +1 -0
  49. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts +9 -0
  50. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +1 -0
  51. package/lib/typescript/network/index.d.ts +12 -0
  52. package/lib/typescript/network/index.d.ts.map +1 -0
  53. package/lib/typescript/network/types/index.d.ts +88 -0
  54. package/lib/typescript/network/types/index.d.ts.map +1 -0
  55. package/lib/typescript/network/utils/extractOperationName.d.ts +41 -0
  56. package/lib/typescript/network/utils/extractOperationName.d.ts.map +1 -0
  57. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts +79 -0
  58. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +1 -0
  59. package/lib/typescript/network/utils/formatting.d.ts +6 -0
  60. package/lib/typescript/network/utils/formatting.d.ts.map +1 -0
  61. package/lib/typescript/network/utils/networkEventStore.d.ts +81 -0
  62. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -0
  63. package/lib/typescript/network/utils/networkListener.d.ts +191 -0
  64. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -0
  65. package/lib/typescript/preset.d.ts +76 -0
  66. package/lib/typescript/preset.d.ts.map +1 -0
  67. package/package.json +69 -0
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.formatGraphQLDisplay = formatGraphQLDisplay;
7
+ exports.formatGraphQLVariables = formatGraphQLVariables;
8
+ exports.searchGraphQLVariables = searchGraphQLVariables;
9
+ /**
10
+ * Format GraphQL variables for display using arrow notation
11
+ *
12
+ * Mimics React Query key display format to provide visual consistency:
13
+ * - React Query: ["pokemon", "Sandshrew"] → "pokemon › Sandshrew"
14
+ * - GraphQL: GetPokemon(id: "Sandshrew") → "GetPokemon › Sandshrew"
15
+ *
16
+ * Extracts only the values from variables (not keys) and formats them
17
+ * for clean, compact display in the network request list.
18
+ */
19
+
20
+ /**
21
+ * Format a single variable value for display
22
+ *
23
+ * @param value - The variable value to format
24
+ * @param maxLength - Maximum length for string values (default: 30)
25
+ * @returns Formatted string or null if value should be skipped
26
+ */
27
+ function formatValue(value, maxLength = 30) {
28
+ // Null/undefined - skip
29
+ if (value === null || value === undefined) {
30
+ return null;
31
+ }
32
+
33
+ // Boolean
34
+ if (typeof value === 'boolean') {
35
+ return String(value);
36
+ }
37
+
38
+ // Number
39
+ if (typeof value === 'number') {
40
+ return String(value);
41
+ }
42
+
43
+ // String
44
+ if (typeof value === 'string') {
45
+ // Truncate long strings
46
+ if (value.length > maxLength) {
47
+ return value.substring(0, maxLength - 1) + '…';
48
+ }
49
+ return value;
50
+ }
51
+
52
+ // Array - take first value or show count
53
+ if (Array.isArray(value)) {
54
+ if (value.length === 0) return null;
55
+ if (value.length === 1) {
56
+ return formatValue(value[0], maxLength);
57
+ }
58
+ // Multiple items - show count
59
+ return `${value.length} items`;
60
+ }
61
+
62
+ // Object - extract first string/number value
63
+ if (typeof value === 'object') {
64
+ const entries = Object.entries(value);
65
+ if (entries.length === 0) return null;
66
+
67
+ // Find first meaningful value
68
+ for (const [_, v] of entries) {
69
+ const formatted = formatValue(v, maxLength);
70
+ if (formatted) {
71
+ return formatted;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * Extract variable values from GraphQL variables object
81
+ *
82
+ * Returns an array of formatted values for display with arrow notation.
83
+ *
84
+ * @param variables - GraphQL variables object
85
+ * @param maxValues - Maximum number of values to show (default: 3)
86
+ * @returns Array of formatted variable values
87
+ *
88
+ * @example
89
+ * formatGraphQLVariables({ id: "pikachu" })
90
+ * // Returns: ["pikachu"]
91
+ *
92
+ * @example
93
+ * formatGraphQLVariables({ userId: 123, includeProfile: true })
94
+ * // Returns: ["123", "true"]
95
+ *
96
+ * @example
97
+ * formatGraphQLVariables({ filter: { status: "active" }, limit: 10 })
98
+ * // Returns: ["active", "10"]
99
+ */
100
+ function formatGraphQLVariables(variables, maxValues = 3) {
101
+ if (!variables || typeof variables !== 'object') {
102
+ return null;
103
+ }
104
+ const values = [];
105
+
106
+ // Extract values from variables object
107
+ for (const [_, value] of Object.entries(variables)) {
108
+ const formatted = formatValue(value);
109
+ if (formatted) {
110
+ values.push(formatted);
111
+ }
112
+
113
+ // Stop if we've reached max values
114
+ if (values.length >= maxValues) {
115
+ break;
116
+ }
117
+ }
118
+ return values.length > 0 ? values : null;
119
+ }
120
+
121
+ /**
122
+ * Combine operation name with variables using arrow notation
123
+ *
124
+ * Matches React Query display pattern: "pokemon › Sandshrew"
125
+ * This provides visual consistency across all dev tools.
126
+ *
127
+ * @param operationName - GraphQL operation name (e.g., "GetPokemon")
128
+ * @param variables - GraphQL variables object
129
+ * @returns Formatted string with arrow notation
130
+ *
131
+ * @example
132
+ * formatGraphQLDisplay("GetPokemon", { id: "Sandshrew" })
133
+ * // Returns: "GetPokemon › Sandshrew"
134
+ *
135
+ * @example
136
+ * formatGraphQLDisplay("GetUser", { userId: 123, includeProfile: true })
137
+ * // Returns: "GetUser › 123 › true"
138
+ *
139
+ * @example
140
+ * formatGraphQLDisplay("GetPosts", { status: "published", limit: 10, offset: 0 })
141
+ * // Returns: "GetPosts › published › 10 › 0" (first 3 values by default)
142
+ *
143
+ * @example
144
+ * formatGraphQLDisplay("GetCurrentUser", {})
145
+ * // Returns: "GetCurrentUser" (no variables)
146
+ */
147
+ function formatGraphQLDisplay(operationName, variables) {
148
+ const values = formatGraphQLVariables(variables);
149
+ if (!values || values.length === 0) {
150
+ // No variables - just operation name
151
+ return operationName;
152
+ }
153
+
154
+ // Combine: "GetPokemon › Sandshrew" (matches React Query pattern)
155
+ return [operationName, ...values].join(" › ");
156
+ }
157
+
158
+ /**
159
+ * Search GraphQL variables for a given text
160
+ *
161
+ * Recursively searches through all variable values to find matches.
162
+ * Used for filtering GraphQL requests by variable content.
163
+ *
164
+ * @param variables - GraphQL variables object
165
+ * @param searchText - Text to search for (already lowercased)
166
+ * @returns true if search text found in any variable value
167
+ *
168
+ * @example
169
+ * searchGraphQLVariables({ id: "pikachu" }, "pika")
170
+ * // Returns: true
171
+ *
172
+ * @example
173
+ * searchGraphQLVariables({ userId: 123, name: "John" }, "123")
174
+ * // Returns: true
175
+ */
176
+ function searchGraphQLVariables(variables, searchText) {
177
+ if (!variables || typeof variables !== 'object') {
178
+ return false;
179
+ }
180
+
181
+ // Search through all variable values
182
+ for (const value of Object.values(variables)) {
183
+ if (searchValue(value, searchText)) {
184
+ return true;
185
+ }
186
+ }
187
+ return false;
188
+ }
189
+
190
+ /**
191
+ * Recursively search a value for matching text
192
+ */
193
+ function searchValue(value, searchText) {
194
+ // String - direct match
195
+ if (typeof value === 'string') {
196
+ return value.toLowerCase().includes(searchText);
197
+ }
198
+
199
+ // Number - convert to string and match
200
+ if (typeof value === 'number') {
201
+ return String(value).includes(searchText);
202
+ }
203
+
204
+ // Boolean - convert to string and match
205
+ if (typeof value === 'boolean') {
206
+ return String(value).includes(searchText);
207
+ }
208
+
209
+ // Array - search each item
210
+ if (Array.isArray(value)) {
211
+ return value.some(item => searchValue(item, searchText));
212
+ }
213
+
214
+ // Object - search nested values
215
+ if (typeof value === 'object' && value !== null) {
216
+ return Object.values(value).some(v => searchValue(v, searchText));
217
+ }
218
+ return false;
219
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "formatBytes", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _sharedUi.formatBytes;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "formatDuration", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _sharedUi.formatDuration;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "formatHttpStatus", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _sharedUi.formatHttpStatus;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "getMethodColor", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _sharedUi.getMethodColor;
28
+ }
29
+ });
30
+ var _sharedUi = require("@buoy-gg/shared-ui");
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.networkEventStore = void 0;
7
+ var _extractOperationName = require("./extractOperationName");
8
+ /**
9
+ * Network event store for managing captured network requests
10
+ * Works with the Reactotron-style network listener
11
+ */
12
+
13
+ class NetworkEventStore {
14
+ events = [];
15
+ pendingRequests = new Map();
16
+ listeners = new Set();
17
+ maxEvents = 500; // Configurable max events to prevent memory issues
18
+ recentRequests = new Map(); // Track recent requests to detect duplicates
19
+
20
+ /**
21
+ * Process a network listener event
22
+ */
23
+ processNetworkEvent(event) {
24
+ const {
25
+ request
26
+ } = event;
27
+ if (event.type === "request") {
28
+ // Check for duplicate request based on URL, method, and timing
29
+ const requestKey = `${request.method}:${request.url}`;
30
+ const now = Date.now();
31
+ const lastRequestTime = this.recentRequests.get(requestKey);
32
+
33
+ // If same request within 50ms, likely a duplicate from XHR/fetch dual interception
34
+ if (lastRequestTime && now - lastRequestTime < 50) {
35
+ return; // Skip duplicate
36
+ }
37
+ this.recentRequests.set(requestKey, now);
38
+
39
+ // Clean up old entries to prevent memory leak
40
+ if (this.recentRequests.size > 100) {
41
+ const cutoff = now - 5000; // Remove entries older than 5 seconds
42
+ for (const [key, time] of this.recentRequests.entries()) {
43
+ if (time < cutoff) {
44
+ this.recentRequests.delete(key);
45
+ }
46
+ }
47
+ }
48
+
49
+ // Create new network event for request
50
+ // Build full URL with query params if present
51
+ const queryString = request.params ? `?${new URLSearchParams(request.params).toString()}` : "";
52
+ const fullUrl = `${request.url}${queryString}`;
53
+
54
+ // Extract GraphQL operation name and variables for searchability
55
+ let operationName;
56
+ let graphqlVariables;
57
+ if (request.client === 'graphql') {
58
+ const extracted = (0, _extractOperationName.extractOperationName)(request.data);
59
+ 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') {
63
+ graphqlVariables = request.data.variables;
64
+ }
65
+ }
66
+ const networkEvent = {
67
+ id: request.id,
68
+ method: request.method,
69
+ url: fullUrl,
70
+ // Store FULL URL with query params
71
+ host: this.extractHost(request.url),
72
+ path: this.extractPath(request.url),
73
+ query: queryString,
74
+ timestamp: event.timestamp.getTime(),
75
+ requestHeaders: request.headers || {},
76
+ requestData: request.data,
77
+ requestSize: this.getDataSize(request.data),
78
+ responseHeaders: {},
79
+ requestClient: request.client,
80
+ operationName,
81
+ // GraphQL operation name for search/filter
82
+ graphqlVariables // GraphQL variables for display and search
83
+ };
84
+
85
+ // Store as pending
86
+ this.pendingRequests.set(request.id, networkEvent);
87
+
88
+ // Add to events list
89
+ this.events = [networkEvent, ...this.events].slice(0, this.maxEvents);
90
+ this.notifyListeners();
91
+ } else if (event.type === "response" || event.type === "error") {
92
+ // Find and update the pending request
93
+ const index = this.events.findIndex(e => e.id === request.id);
94
+ if (index !== -1) {
95
+ const updatedEvent = {
96
+ ...this.events[index],
97
+ duration: event.duration
98
+ };
99
+ if (event.response) {
100
+ updatedEvent.status = event.response.status;
101
+ updatedEvent.statusText = event.response.statusText;
102
+ updatedEvent.responseHeaders = event.response.headers || {};
103
+ updatedEvent.responseData = event.response.body;
104
+ updatedEvent.responseSize = event.response.size || 0;
105
+ updatedEvent.responseType = event.response.headers?.["content-type"];
106
+ }
107
+ if (event.error) {
108
+ updatedEvent.error = event.error.message;
109
+ updatedEvent.status = updatedEvent.status || 0;
110
+ }
111
+ this.events[index] = updatedEvent;
112
+ this.pendingRequests.delete(request.id);
113
+ this.notifyListeners();
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Extract host from URL
120
+ */
121
+ extractHost(url) {
122
+ try {
123
+ const urlObj = new URL(url);
124
+ // @ts-ignore - this does exist on native
125
+ return urlObj.hostname;
126
+ } catch {
127
+ return "";
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Extract path from URL
133
+ */
134
+ extractPath(url) {
135
+ try {
136
+ const urlObj = new URL(url);
137
+ // @ts-ignore - this does exist on native
138
+ return urlObj.pathname;
139
+ } catch {
140
+ return url;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Get size of data
146
+ */
147
+ getDataSize(data) {
148
+ if (!data) return 0;
149
+ if (typeof data === "string") return data.length;
150
+ try {
151
+ return JSON.stringify(data).length;
152
+ } catch {
153
+ return 0;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Get all events
159
+ */
160
+ getEvents() {
161
+ return [...this.events];
162
+ }
163
+
164
+ /**
165
+ * Get event by ID
166
+ */
167
+ getEventById(id) {
168
+ return this.events.find(e => e.id === id);
169
+ }
170
+
171
+ /**
172
+ * Clear all events
173
+ */
174
+ clearEvents() {
175
+ this.events = [];
176
+ this.pendingRequests.clear();
177
+ 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
+ }
190
+
191
+ /**
192
+ * Notify all listeners of changes
193
+ */
194
+ notifyListeners() {
195
+ const events = this.getEvents();
196
+ this.listeners.forEach(listener => listener(events));
197
+ }
198
+
199
+ /**
200
+ * Set maximum number of events to store
201
+ */
202
+ setMaxEvents(max) {
203
+ this.maxEvents = max;
204
+ if (this.events.length > max) {
205
+ this.events = this.events.slice(0, max);
206
+ this.notifyListeners();
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Get statistics about network events
212
+ */
213
+ getStats() {
214
+ const total = this.events.length;
215
+ const successful = this.events.filter(e => e.status && e.status >= 200 && e.status < 300).length;
216
+ const failed = this.events.filter(e => e.error || e.status && e.status >= 400).length;
217
+ const pending = this.events.filter(e => !e.status && !e.error).length;
218
+ const durations = this.events.filter(e => e.duration).map(e => e.duration);
219
+ const avgDuration = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;
220
+ const totalSent = this.events.reduce((sum, e) => sum + (e.requestSize || 0), 0);
221
+ const totalReceived = this.events.reduce((sum, e) => sum + (e.responseSize || 0), 0);
222
+ return {
223
+ totalRequests: total,
224
+ successfulRequests: successful,
225
+ failedRequests: failed,
226
+ pendingRequests: pending,
227
+ totalDataSent: totalSent,
228
+ totalDataReceived: totalReceived,
229
+ averageDuration: Math.round(avgDuration)
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Filter events by criteria
235
+ */
236
+ filterEvents(filter) {
237
+ let filtered = [...this.events];
238
+ if (filter.method) {
239
+ filtered = filtered.filter(e => e.method === filter.method);
240
+ }
241
+ if (filter.status) {
242
+ switch (filter.status) {
243
+ case "success":
244
+ filtered = filtered.filter(e => e.status && e.status >= 200 && e.status < 300);
245
+ break;
246
+ case "error":
247
+ filtered = filtered.filter(e => e.error || e.status && e.status >= 400);
248
+ break;
249
+ case "pending":
250
+ filtered = filtered.filter(e => !e.status && !e.error);
251
+ break;
252
+ }
253
+ }
254
+ if (filter.searchText) {
255
+ const search = filter.searchText.toLowerCase();
256
+ filtered = filtered.filter(e => e.url.toLowerCase().includes(search) || e.method.toLowerCase().includes(search) || e.error && e.error.toLowerCase().includes(search));
257
+ }
258
+ if (filter.host) {
259
+ filtered = filtered.filter(e => e.host === filter.host);
260
+ }
261
+ return filtered;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Singleton store that aggregates captured network traffic. Components and hooks consume this
267
+ * store to render histories, derive stats, and subscribe to real-time updates.
268
+ */
269
+ const networkEventStore = exports.networkEventStore = new NetworkEventStore();