@capillarytech/cap-ui-dev-tools 1.4.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.
@@ -1,434 +0,0 @@
1
- /**
2
- * RRWeb Network Replay Plugin
3
- * Displays network activity in a floating panel during replay
4
- * Based on: https://github.com/rrweb-io/rrweb/pull/1689
5
- */
6
- (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
- typeof define === 'function' && define.amd ? define(factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.rrwebPluginNetworkReplay = factory());
10
- })(this, (function () {
11
- 'use strict';
12
-
13
- function getReplayNetworkPlugin(options) {
14
- const opts = options || {};
15
- const showPanel = opts.showPanel !== false;
16
- const maxEntries = opts.maxEntries || 100;
17
- const position = opts.position || 'bottom-right';
18
-
19
- // Network entries storage
20
- const networkEntries = [];
21
- let panelElement = null;
22
- let tableBody = null;
23
- let isMinimized = false;
24
-
25
- // Helper: Format duration
26
- function formatDuration(ms) {
27
- if (typeof ms !== 'number' || isNaN(ms)) return '-';
28
- if (ms < 1000) return ms + 'ms';
29
- return (ms / 1000).toFixed(2) + 's';
30
- }
31
-
32
- // Helper: Format size
33
- function formatSize(bytes) {
34
- if (typeof bytes !== 'number' || isNaN(bytes)) return '-';
35
- if (bytes < 1024) return bytes + 'B';
36
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + 'KB';
37
- return (bytes / (1024 * 1024)).toFixed(2) + 'MB';
38
- }
39
-
40
- // Helper: Truncate URL for display
41
- function truncateURL(url, maxLength) {
42
- if (!url) return '-';
43
- if (url.length <= maxLength) return url;
44
- return url.substring(0, maxLength - 3) + '...';
45
- }
46
-
47
- // Helper: Get status color
48
- function getStatusColor(status) {
49
- if (!status) return '#666';
50
- if (status >= 200 && status < 300) return '#28a745';
51
- if (status >= 300 && status < 400) return '#ffc107';
52
- if (status >= 400 && status < 500) return '#dc3545';
53
- if (status >= 500) return '#dc3545';
54
- return '#666';
55
- }
56
-
57
- // Helper: Get method color
58
- function getMethodColor(method) {
59
- if (!method) return '#666';
60
- const upperMethod = method.toUpperCase();
61
- if (upperMethod === 'GET') return '#007bff';
62
- if (upperMethod === 'POST') return '#28a745';
63
- if (upperMethod === 'PUT') return '#ffc107';
64
- if (upperMethod === 'DELETE') return '#dc3545';
65
- if (upperMethod === 'PATCH') return '#17a2b8';
66
- return '#666';
67
- }
68
-
69
- // Create panel HTML
70
- function createPanel() {
71
- if (!showPanel) return null;
72
-
73
- const panel = document.createElement('div');
74
- panel.id = 'rrweb-network-panel';
75
- panel.style.cssText = `
76
- position: fixed;
77
- ${position.includes('right') ? 'right: 20px;' : 'left: 20px;'}
78
- ${position.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'}
79
- width: 600px;
80
- max-height: 500px;
81
- background: white;
82
- border: 1px solid #ddd;
83
- border-radius: 8px;
84
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
85
- z-index: 10000;
86
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
87
- font-size: 12px;
88
- display: flex;
89
- flex-direction: column;
90
- overflow: hidden;
91
- `;
92
-
93
- // Header
94
- const header = document.createElement('div');
95
- header.style.cssText = `
96
- padding: 12px 16px;
97
- background: #f8f9fa;
98
- border-bottom: 1px solid #ddd;
99
- display: flex;
100
- justify-content: space-between;
101
- align-items: center;
102
- cursor: move;
103
- user-select: none;
104
- `;
105
-
106
- const title = document.createElement('div');
107
- title.textContent = '🌐 Network Activity';
108
- title.style.cssText = 'font-weight: 600; font-size: 14px; color: #333;';
109
-
110
- const controls = document.createElement('div');
111
- controls.style.cssText = 'display: flex; gap: 8px; align-items: center;';
112
-
113
- const clearBtn = document.createElement('button');
114
- clearBtn.textContent = 'Clear';
115
- clearBtn.style.cssText = `
116
- padding: 4px 8px;
117
- font-size: 11px;
118
- border: 1px solid #ddd;
119
- background: white;
120
- border-radius: 4px;
121
- cursor: pointer;
122
- `;
123
- clearBtn.onclick = () => {
124
- networkEntries.length = 0;
125
- updateTable();
126
- };
127
-
128
- const minimizeBtn = document.createElement('button');
129
- minimizeBtn.textContent = isMinimized ? '▼' : '▲';
130
- minimizeBtn.style.cssText = `
131
- padding: 4px 8px;
132
- font-size: 11px;
133
- border: 1px solid #ddd;
134
- background: white;
135
- border-radius: 4px;
136
- cursor: pointer;
137
- min-width: 24px;
138
- `;
139
- minimizeBtn.onclick = () => {
140
- isMinimized = !isMinimized;
141
- minimizeBtn.textContent = isMinimized ? '▼' : '▲';
142
- content.style.display = isMinimized ? 'none' : 'flex';
143
- };
144
-
145
- controls.appendChild(clearBtn);
146
- controls.appendChild(minimizeBtn);
147
- header.appendChild(title);
148
- header.appendChild(controls);
149
-
150
- // Content area
151
- const content = document.createElement('div');
152
- content.id = 'rrweb-network-content';
153
- content.style.cssText = `
154
- flex: 1;
155
- overflow-y: auto;
156
- display: flex;
157
- flex-direction: column;
158
- `;
159
-
160
- // Table
161
- const table = document.createElement('table');
162
- table.style.cssText = `
163
- width: 100%;
164
- border-collapse: collapse;
165
- font-size: 11px;
166
- `;
167
-
168
- // Table header
169
- const thead = document.createElement('thead');
170
- thead.style.cssText = 'background: #f8f9fa; position: sticky; top: 0; z-index: 10;';
171
- const headerRow = document.createElement('tr');
172
- ['Method', 'URL', 'Status', 'Duration', 'Size'].forEach(text => {
173
- const th = document.createElement('th');
174
- th.textContent = text;
175
- th.style.cssText = 'padding: 8px; text-align: left; font-weight: 600; border-bottom: 2px solid #ddd;';
176
- headerRow.appendChild(th);
177
- });
178
- thead.appendChild(headerRow);
179
-
180
- // Table body
181
- tableBody = document.createElement('tbody');
182
- table.appendChild(thead);
183
- table.appendChild(tableBody);
184
- content.appendChild(table);
185
-
186
- panel.appendChild(header);
187
- panel.appendChild(content);
188
-
189
- // Make panel draggable
190
- let isDragging = false;
191
- let currentX, currentY, initialX, initialY;
192
-
193
- header.addEventListener('mousedown', (e) => {
194
- isDragging = true;
195
- initialX = e.clientX - panel.offsetLeft;
196
- initialY = e.clientY - panel.offsetTop;
197
- });
198
-
199
- document.addEventListener('mousemove', (e) => {
200
- if (isDragging) {
201
- currentX = e.clientX - initialX;
202
- currentY = e.clientY - initialY;
203
- panel.style.left = currentX + 'px';
204
- panel.style.top = currentY + 'px';
205
- panel.style.right = 'auto';
206
- panel.style.bottom = 'auto';
207
- }
208
- });
209
-
210
- document.addEventListener('mouseup', () => {
211
- isDragging = false;
212
- });
213
-
214
- document.body.appendChild(panel);
215
- return panel;
216
- }
217
-
218
- // Update table with network entries
219
- function updateTable() {
220
- if (!tableBody) return;
221
-
222
- // Clear existing rows
223
- tableBody.innerHTML = '';
224
-
225
- // Show latest entries (up to maxEntries)
226
- const entriesToShow = networkEntries.slice(-maxEntries);
227
-
228
- entriesToShow.forEach(entry => {
229
- const row = document.createElement('tr');
230
- row.style.cssText = 'border-bottom: 1px solid #eee;';
231
-
232
- // Method cell
233
- const methodCell = document.createElement('td');
234
- methodCell.textContent = entry.method || '-';
235
- methodCell.style.cssText = `padding: 6px 8px; color: ${getMethodColor(entry.method)}; font-weight: 600;`;
236
-
237
- // URL cell
238
- const urlCell = document.createElement('td');
239
- urlCell.textContent = truncateURL(entry.url, 40);
240
- urlCell.title = entry.url || '';
241
- urlCell.style.cssText = 'padding: 6px 8px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;';
242
-
243
- // Status cell
244
- const statusCell = document.createElement('td');
245
- statusCell.textContent = entry.status || '-';
246
- statusCell.style.cssText = `padding: 6px 8px; color: ${getStatusColor(entry.status)}; font-weight: 600;`;
247
-
248
- // Duration cell
249
- const durationCell = document.createElement('td');
250
- durationCell.textContent = formatDuration(entry.duration);
251
- durationCell.style.cssText = 'padding: 6px 8px;';
252
-
253
- // Size cell
254
- const sizeCell = document.createElement('td');
255
- sizeCell.textContent = formatSize(entry.transferSize || entry.encodedBodySize);
256
- sizeCell.style.cssText = 'padding: 6px 8px;';
257
-
258
- row.appendChild(methodCell);
259
- row.appendChild(urlCell);
260
- row.appendChild(statusCell);
261
- row.appendChild(durationCell);
262
- row.appendChild(sizeCell);
263
-
264
- tableBody.appendChild(row);
265
- });
266
-
267
- // Scroll to bottom
268
- if (tableBody.parentElement) {
269
- const content = tableBody.parentElement.parentElement;
270
- if (content) {
271
- content.scrollTop = content.scrollHeight;
272
- }
273
- }
274
- }
275
-
276
- // Process network event
277
- function processNetworkEvent(event) {
278
- // Only handle plugin events for network
279
- if (!event || event.type !== 6 || !event.data) return;
280
- if (event.data?.plugin !== 'rrweb/network@1') return;
281
-
282
- // Try to get payload - handle nested structure (double-wrapped during RRWeb serialization)
283
- let payload = null;
284
-
285
- // Check for nested structure first (double-wrapped during RRWeb serialization)
286
- if (event.data?.payload?.data?.payload) {
287
- const nestedPayload = event.data.payload.data.payload;
288
- if (nestedPayload.type === 'request' || nestedPayload.type === 'response') {
289
- payload = nestedPayload;
290
- }
291
- }
292
-
293
- // Fallback: Check for normal structure
294
- if (!payload && event.data?.payload) {
295
- const testPayload = event.data.payload;
296
- if (testPayload.type === 'request' || testPayload.type === 'response') {
297
- payload = testPayload;
298
- }
299
- }
300
-
301
- if (!payload) return;
302
-
303
- // Verify payload has the expected structure
304
- if (typeof payload.type !== 'string' || (payload.type !== 'request' && payload.type !== 'response')) {
305
- return;
306
- }
307
-
308
- if (payload.type === 'request') {
309
- // Create panel if not exists and showPanel is enabled
310
- if (showPanel && !panelElement) {
311
- panelElement = createPanel();
312
- }
313
-
314
- // Store request entry
315
- const entry = {
316
- id: payload.id,
317
- method: payload.method || 'GET',
318
- url: payload.url || '',
319
- timestamp: payload.timestamp || event.timestamp,
320
- requestType: payload.requestType || 'unknown'
321
- };
322
-
323
- // Find existing entry or create new
324
- const existingIndex = networkEntries.findIndex(e => e.id === entry.id);
325
- if (existingIndex >= 0) {
326
- Object.assign(networkEntries[existingIndex], entry);
327
- } else {
328
- networkEntries.push(entry);
329
- }
330
-
331
- // Limit entries
332
- if (networkEntries.length > maxEntries * 2) {
333
- networkEntries.shift();
334
- }
335
-
336
- // Update table if panel is shown
337
- if (showPanel) {
338
- updateTable();
339
- }
340
- } else if (payload.type === 'response') {
341
- // Create panel if not exists and showPanel is enabled
342
- if (showPanel && !panelElement) {
343
- panelElement = createPanel();
344
- }
345
-
346
- // Update existing entry with response data
347
- const entryIndex = networkEntries.findIndex(e => e.id === payload.id);
348
- if (entryIndex >= 0) {
349
- const entry = networkEntries[entryIndex];
350
- entry.status = payload.status;
351
- entry.statusText = payload.statusText;
352
- entry.duration = payload.duration;
353
- entry.error = payload.error;
354
-
355
- if (payload.performance) {
356
- entry.transferSize = payload.performance.transferSize;
357
- entry.encodedBodySize = payload.performance.encodedBodySize;
358
- entry.decodedBodySize = payload.performance.decodedBodySize;
359
- }
360
-
361
- // Update table if panel is shown
362
- if (showPanel) {
363
- updateTable();
364
- }
365
- } else {
366
- // Response without request (shouldn't happen, but handle it)
367
- // Create panel if not exists and showPanel is enabled
368
- if (showPanel && !panelElement) {
369
- panelElement = createPanel();
370
- }
371
-
372
- const entry = {
373
- id: payload.id,
374
- method: 'UNKNOWN',
375
- url: '',
376
- status: payload.status,
377
- statusText: payload.statusText,
378
- duration: payload.duration,
379
- timestamp: payload.timestamp || event.timestamp,
380
- error: payload.error
381
- };
382
-
383
- if (payload.performance) {
384
- entry.transferSize = payload.performance.transferSize;
385
- entry.encodedBodySize = payload.performance.encodedBodySize;
386
- entry.decodedBodySize = payload.performance.decodedBodySize;
387
- }
388
-
389
- networkEntries.push(entry);
390
-
391
- if (networkEntries.length > maxEntries * 2) {
392
- networkEntries.shift();
393
- }
394
-
395
- // Update table if panel is shown
396
- if (showPanel) {
397
- updateTable();
398
- }
399
- }
400
- }
401
- }
402
-
403
- return {
404
- name: 'rrweb/network@1',
405
- handler(event, isSync, context) {
406
- // Only handle plugin events for network
407
- if (event.type !== 6 || event.data?.plugin !== 'rrweb/network@1') {
408
- return;
409
- }
410
-
411
- // Process the network event
412
- processNetworkEvent(event);
413
- },
414
- onBuild() {
415
- // Create panel when player is built
416
- if (showPanel && !panelElement) {
417
- panelElement = createPanel();
418
- }
419
- },
420
- onDestroy() {
421
- // Cleanup panel when player is destroyed
422
- if (panelElement && panelElement.parentElement) {
423
- panelElement.parentElement.removeChild(panelElement);
424
- panelElement = null;
425
- tableBody = null;
426
- }
427
- networkEntries.length = 0;
428
- }
429
- };
430
- }
431
-
432
- // Export function
433
- return getReplayNetworkPlugin;
434
- }));