network-terminal 1.0.9 → 1.0.11

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/bin/cli.js CHANGED
@@ -1,20 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const http = require('http');
4
- const path = require('path');
5
4
 
6
5
  const args = process.argv.slice(2);
7
-
8
- // Parse arguments
9
6
  const portIndex = args.indexOf('--port');
10
- const proxyIndex = args.indexOf('--proxy');
11
- const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : 3001;
12
- const targetPort = proxyIndex !== -1 ? parseInt(args[proxyIndex + 1], 10) : 3000;
13
- const dashboardPort = port + 1000; // Dashboard on separate port
7
+ const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : 4001;
14
8
 
15
9
  // Store network logs
16
10
  const networkLogs = [];
17
- let logId = 0;
18
11
 
19
12
  // SSE clients for real-time updates
20
13
  const sseClients = new Set();
@@ -26,7 +19,132 @@ function broadcastLog(log) {
26
19
  });
27
20
  }
28
21
 
29
- // Dashboard HTML with two terminals
22
+ // Monitor script that users add to their app
23
+ const monitorScript = `
24
+ (function() {
25
+ if (window.__networkTerminalActive) return;
26
+ window.__networkTerminalActive = true;
27
+
28
+ const DASHBOARD_URL = 'http://localhost:${port}';
29
+
30
+ function sendLog(log) {
31
+ var xhr = new XMLHttpRequest();
32
+ xhr.open('POST', DASHBOARD_URL + '/log', true);
33
+ xhr.setRequestHeader('Content-Type', 'application/json');
34
+ xhr.send(JSON.stringify(log));
35
+ }
36
+
37
+ function tryParse(data) {
38
+ if (!data) return null;
39
+ if (typeof data === 'object') return data;
40
+ try { return JSON.parse(data); } catch (e) { return data; }
41
+ }
42
+
43
+ // Intercept Fetch
44
+ var originalFetch = window.fetch;
45
+ window.fetch = function() {
46
+ var args = arguments;
47
+ var startTime = Date.now();
48
+ var input = args[0];
49
+ var options = args[1] || {};
50
+ var url = typeof input === 'string' ? input : (input.url || input.toString());
51
+ var method = (options.method || 'GET').toUpperCase();
52
+
53
+ // Skip our own requests
54
+ if (url.includes('localhost:${port}')) {
55
+ return originalFetch.apply(this, args);
56
+ }
57
+
58
+ var log = {
59
+ id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
60
+ method: method,
61
+ url: url,
62
+ timestamp: new Date().toISOString(),
63
+ requestBody: options.body ? tryParse(options.body) : null,
64
+ status: null,
65
+ responseBody: null,
66
+ duration: null,
67
+ };
68
+
69
+ sendLog(log);
70
+
71
+ return originalFetch.apply(this, args).then(function(response) {
72
+ log.status = response.status;
73
+ log.duration = Date.now() - startTime;
74
+
75
+ var cloned = response.clone();
76
+ cloned.text().then(function(text) {
77
+ log.responseBody = tryParse(text);
78
+ sendLog(log);
79
+ }).catch(function() {
80
+ sendLog(log);
81
+ });
82
+
83
+ return response;
84
+ }).catch(function(error) {
85
+ log.status = 0;
86
+ log.duration = Date.now() - startTime;
87
+ log.error = error.message;
88
+ sendLog(log);
89
+ throw error;
90
+ });
91
+ };
92
+
93
+ // Intercept XMLHttpRequest
94
+ var OriginalXHR = window.XMLHttpRequest;
95
+ window.XMLHttpRequest = function() {
96
+ var xhr = new OriginalXHR();
97
+ var method, url, requestBody, startTime, logId;
98
+
99
+ var originalOpen = xhr.open;
100
+ xhr.open = function(m, u) {
101
+ method = m;
102
+ url = u;
103
+ return originalOpen.apply(this, arguments);
104
+ };
105
+
106
+ var originalSend = xhr.send;
107
+ xhr.send = function(body) {
108
+ // Skip our own requests
109
+ if (url && url.includes('localhost:${port}')) {
110
+ return originalSend.apply(this, arguments);
111
+ }
112
+
113
+ startTime = Date.now();
114
+ requestBody = body;
115
+ logId = Date.now().toString() + Math.random().toString(36).substr(2, 9);
116
+
117
+ var log = {
118
+ id: logId,
119
+ method: (method || 'GET').toUpperCase(),
120
+ url: url,
121
+ timestamp: new Date().toISOString(),
122
+ requestBody: tryParse(requestBody),
123
+ status: null,
124
+ responseBody: null,
125
+ duration: null,
126
+ };
127
+
128
+ sendLog(log);
129
+
130
+ xhr.addEventListener('loadend', function() {
131
+ log.status = xhr.status;
132
+ log.duration = Date.now() - startTime;
133
+ log.responseBody = tryParse(xhr.responseText);
134
+ sendLog(log);
135
+ });
136
+
137
+ return originalSend.apply(this, arguments);
138
+ };
139
+
140
+ return xhr;
141
+ };
142
+
143
+ console.log('[Network Terminal] ✓ Monitoring active - Dashboard: http://localhost:${port}');
144
+ })();
145
+ `;
146
+
147
+ // Dashboard HTML
30
148
  const dashboardHTML = `
31
149
  <!DOCTYPE html>
32
150
  <html lang="en">
@@ -51,19 +169,9 @@ const dashboardHTML = `
51
169
  justify-content: space-between;
52
170
  align-items: center;
53
171
  }
54
- .title {
55
- color: #4ade80;
56
- font-size: 18px;
57
- font-weight: bold;
58
- }
59
- .subtitle {
60
- color: #64748b;
61
- font-size: 12px;
62
- }
63
- .controls {
64
- display: flex;
65
- gap: 12px;
66
- }
172
+ .title { color: #4ade80; font-size: 18px; font-weight: bold; }
173
+ .subtitle { color: #64748b; font-size: 12px; margin-top: 4px; }
174
+ .controls { display: flex; gap: 12px; align-items: center; }
67
175
  .btn {
68
176
  background: #1e293b;
69
177
  border: 1px solid #334155;
@@ -74,23 +182,15 @@ const dashboardHTML = `
74
182
  font-size: 12px;
75
183
  font-family: inherit;
76
184
  }
77
- .btn:hover {
78
- background: #334155;
79
- color: #e2e8f0;
80
- }
81
- .container {
82
- display: flex;
83
- height: calc(100vh - 60px);
84
- }
185
+ .btn:hover { background: #334155; color: #e2e8f0; }
186
+ .container { display: flex; height: calc(100vh - 80px); }
85
187
  .panel {
86
188
  flex: 1;
87
189
  display: flex;
88
190
  flex-direction: column;
89
191
  border-right: 1px solid #1e293b;
90
192
  }
91
- .panel:last-child {
92
- border-right: none;
93
- }
193
+ .panel:last-child { border-right: none; }
94
194
  .panel-header {
95
195
  background: #0f172a;
96
196
  padding: 12px 16px;
@@ -99,10 +199,7 @@ const dashboardHTML = `
99
199
  justify-content: space-between;
100
200
  align-items: center;
101
201
  }
102
- .panel-title {
103
- font-weight: bold;
104
- font-size: 14px;
105
- }
202
+ .panel-title { font-weight: bold; font-size: 14px; }
106
203
  .panel-title.request { color: #3b82f6; }
107
204
  .panel-title.response { color: #22c55e; }
108
205
  .panel-count {
@@ -112,11 +209,7 @@ const dashboardHTML = `
112
209
  font-size: 11px;
113
210
  color: #64748b;
114
211
  }
115
- .logs {
116
- flex: 1;
117
- overflow-y: auto;
118
- padding: 8px;
119
- }
212
+ .logs { flex: 1; overflow-y: auto; padding: 8px; }
120
213
  .log-entry {
121
214
  background: #0f172a;
122
215
  border: 1px solid #1e293b;
@@ -126,12 +219,8 @@ const dashboardHTML = `
126
219
  cursor: pointer;
127
220
  transition: border-color 0.2s;
128
221
  }
129
- .log-entry:hover {
130
- border-color: #334155;
131
- }
132
- .log-entry.selected {
133
- border-color: #4ade80;
134
- }
222
+ .log-entry:hover { border-color: #334155; }
223
+ .log-entry.selected { border-color: #4ade80; }
135
224
  .log-header {
136
225
  display: flex;
137
226
  align-items: center;
@@ -150,11 +239,7 @@ const dashboardHTML = `
150
239
  .method.PUT { background: #a16207; color: #fcd34d; }
151
240
  .method.DELETE { background: #991b1b; color: #fca5a5; }
152
241
  .method.PATCH { background: #7c3aed; color: #c4b5fd; }
153
- .status {
154
- font-size: 11px;
155
- padding: 3px 8px;
156
- border-radius: 4px;
157
- }
242
+ .status { font-size: 11px; padding: 3px 8px; border-radius: 4px; }
158
243
  .status.success { background: #166534; color: #4ade80; }
159
244
  .status.error { background: #991b1b; color: #fca5a5; }
160
245
  .status.pending { background: #374151; color: #9ca3af; }
@@ -166,20 +251,9 @@ const dashboardHTML = `
166
251
  text-overflow: ellipsis;
167
252
  white-space: nowrap;
168
253
  }
169
- .duration {
170
- font-size: 11px;
171
- color: #64748b;
172
- }
173
- .timestamp {
174
- font-size: 10px;
175
- color: #475569;
176
- }
177
- .log-body {
178
- padding: 12px;
179
- font-size: 11px;
180
- max-height: 300px;
181
- overflow: auto;
182
- }
254
+ .duration { font-size: 11px; color: #64748b; }
255
+ .timestamp { font-size: 10px; color: #475569; }
256
+ .log-body { padding: 12px; font-size: 11px; max-height: 300px; overflow: auto; }
183
257
  .log-body pre {
184
258
  margin: 0;
185
259
  white-space: pre-wrap;
@@ -187,10 +261,7 @@ const dashboardHTML = `
187
261
  color: #cbd5e1;
188
262
  line-height: 1.5;
189
263
  }
190
- .log-body.empty {
191
- color: #475569;
192
- font-style: italic;
193
- }
264
+ .log-body.empty { color: #475569; font-style: italic; }
194
265
  .empty-state {
195
266
  display: flex;
196
267
  flex-direction: column;
@@ -198,27 +269,27 @@ const dashboardHTML = `
198
269
  justify-content: center;
199
270
  height: 100%;
200
271
  color: #475569;
272
+ text-align: center;
273
+ padding: 20px;
201
274
  }
202
- .empty-state svg {
203
- width: 48px;
204
- height: 48px;
205
- margin-bottom: 16px;
206
- opacity: 0.5;
207
- }
208
- .connection-status {
209
- display: flex;
210
- align-items: center;
211
- gap: 8px;
212
- font-size: 12px;
213
- }
275
+ .empty-state svg { width: 48px; height: 48px; margin-bottom: 16px; opacity: 0.5; }
214
276
  .status-dot {
215
277
  width: 8px;
216
278
  height: 8px;
217
279
  border-radius: 50%;
218
280
  background: #22c55e;
281
+ animation: pulse 2s infinite;
219
282
  }
220
- .status-dot.disconnected {
221
- background: #ef4444;
283
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
284
+ .status-dot.disconnected { background: #ef4444; animation: none; }
285
+ .code-box {
286
+ background: #1e293b;
287
+ padding: 12px;
288
+ border-radius: 6px;
289
+ margin-top: 12px;
290
+ font-size: 11px;
291
+ color: #4ade80;
292
+ word-break: break-all;
222
293
  }
223
294
  </style>
224
295
  </head>
@@ -226,13 +297,10 @@ const dashboardHTML = `
226
297
  <div class="header">
227
298
  <div>
228
299
  <div class="title">>_ Network Terminal</div>
229
- <div class="subtitle">Monitoring localhost:${targetPort} via proxy :${port}</div>
300
+ <div class="subtitle">Add this to your app's HTML: &lt;script src="http://localhost:${port}/monitor.js"&gt;&lt;/script&gt;</div>
230
301
  </div>
231
302
  <div class="controls">
232
- <div class="connection-status">
233
- <div class="status-dot" id="statusDot"></div>
234
- <span id="statusText">Connected</span>
235
- </div>
303
+ <div class="status-dot" id="statusDot"></div>
236
304
  <button class="btn" onclick="clearLogs()">Clear All</button>
237
305
  </div>
238
306
  </div>
@@ -249,7 +317,8 @@ const dashboardHTML = `
249
317
  <path d="M12 19V5M5 12l7-7 7 7"/>
250
318
  </svg>
251
319
  <div>Waiting for requests...</div>
252
- <div style="margin-top: 8px; font-size: 11px;">Make requests in your app at localhost:${port}</div>
320
+ <div style="margin-top: 12px; font-size: 11px;">Add this script tag to your HTML:</div>
321
+ <div class="code-box">&lt;script src="http://localhost:${port}/monitor.js"&gt;&lt;/script&gt;</div>
253
322
  </div>
254
323
  </div>
255
324
  </div>
@@ -271,17 +340,14 @@ const dashboardHTML = `
271
340
  </div>
272
341
 
273
342
  <script>
274
- const logs = [];
343
+ const logs = new Map();
275
344
  let selectedId = null;
276
345
 
277
346
  function formatJson(data) {
278
347
  if (!data) return null;
279
348
  if (typeof data === 'string') {
280
- try {
281
- data = JSON.parse(data);
282
- } catch (e) {
283
- return escapeHtml(data);
284
- }
349
+ try { data = JSON.parse(data); }
350
+ catch (e) { return escapeHtml(data.substring(0, 2000)); }
285
351
  }
286
352
  return escapeHtml(JSON.stringify(data, null, 2));
287
353
  }
@@ -297,21 +363,29 @@ const dashboardHTML = `
297
363
  String(date.getMilliseconds()).padStart(3, '0');
298
364
  }
299
365
 
366
+ function getLogsArray() {
367
+ return Array.from(logs.values()).sort((a, b) =>
368
+ new Date(b.timestamp) - new Date(a.timestamp)
369
+ );
370
+ }
371
+
300
372
  function renderLogs() {
373
+ const logsArray = getLogsArray();
301
374
  const requestContainer = document.getElementById('requestLogs');
302
375
  const responseContainer = document.getElementById('responseLogs');
303
376
 
304
- document.getElementById('requestCount').textContent = logs.length;
305
- document.getElementById('responseCount').textContent = logs.filter(l => l.status).length;
377
+ document.getElementById('requestCount').textContent = logsArray.length;
378
+ document.getElementById('responseCount').textContent = logsArray.filter(l => l.status).length;
306
379
 
307
- if (logs.length === 0) {
380
+ if (logsArray.length === 0) {
308
381
  requestContainer.innerHTML = \`
309
382
  <div class="empty-state">
310
383
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
311
384
  <path d="M12 19V5M5 12l7-7 7 7"/>
312
385
  </svg>
313
386
  <div>Waiting for requests...</div>
314
- <div style="margin-top: 8px; font-size: 11px;">Make requests in your app at localhost:${port}</div>
387
+ <div style="margin-top: 12px; font-size: 11px;">Add this script tag to your HTML:</div>
388
+ <div class="code-box">&lt;script src="http://localhost:${port}/monitor.js"&gt;&lt;/script&gt;</div>
315
389
  </div>
316
390
  \`;
317
391
  responseContainer.innerHTML = \`
@@ -325,45 +399,33 @@ const dashboardHTML = `
325
399
  return;
326
400
  }
327
401
 
328
- // Render requests
329
- requestContainer.innerHTML = logs.map(log => {
402
+ requestContainer.innerHTML = logsArray.map(log => {
330
403
  const isSelected = selectedId === log.id;
331
404
  const bodyContent = formatJson(log.requestBody);
332
-
333
405
  return \`
334
406
  <div class="log-entry \${isSelected ? 'selected' : ''}" onclick="selectLog('\${log.id}')">
335
407
  <div class="log-header">
336
408
  <span class="method \${log.method}">\${log.method}</span>
337
- <span class="url">\${log.url}</span>
409
+ <span class="url" title="\${escapeHtml(log.url)}">\${log.url}</span>
338
410
  <span class="timestamp">\${formatTime(log.timestamp)}</span>
339
411
  </div>
340
- \${isSelected ? \`
341
- <div class="log-body \${!bodyContent ? 'empty' : ''}">
342
- \${bodyContent ? \`<pre>\${bodyContent}</pre>\` : 'No request body'}
343
- </div>
344
- \` : ''}
412
+ \${isSelected ? \`<div class="log-body \${!bodyContent ? 'empty' : ''}">\${bodyContent ? '<pre>'+bodyContent+'</pre>' : 'No request body'}</div>\` : ''}
345
413
  </div>
346
414
  \`;
347
415
  }).join('');
348
416
 
349
- // Render responses
350
- responseContainer.innerHTML = logs.map(log => {
417
+ responseContainer.innerHTML = logsArray.map(log => {
351
418
  const isSelected = selectedId === log.id;
352
419
  const statusClass = !log.status ? 'pending' : log.status < 400 ? 'success' : 'error';
353
420
  const bodyContent = formatJson(log.responseBody);
354
-
355
421
  return \`
356
422
  <div class="log-entry \${isSelected ? 'selected' : ''}" onclick="selectLog('\${log.id}')">
357
423
  <div class="log-header">
358
424
  <span class="status \${statusClass}">\${log.status || '...'}</span>
359
- <span class="url">\${log.url}</span>
425
+ <span class="url" title="\${escapeHtml(log.url)}">\${log.url}</span>
360
426
  <span class="duration">\${log.duration ? log.duration + 'ms' : ''}</span>
361
427
  </div>
362
- \${isSelected ? \`
363
- <div class="log-body \${!bodyContent ? 'empty' : ''}">
364
- \${bodyContent ? \`<pre>\${bodyContent}</pre>\` : log.status ? 'No response body' : 'Pending...'}
365
- </div>
366
- \` : ''}
428
+ \${isSelected ? \`<div class="log-body \${!bodyContent ? 'empty' : ''}">\${bodyContent ? '<pre>'+bodyContent+'</pre>' : (log.status ? 'No response body' : 'Pending...')}</div>\` : ''}
367
429
  </div>
368
430
  \`;
369
431
  }).join('');
@@ -375,40 +437,32 @@ const dashboardHTML = `
375
437
  }
376
438
 
377
439
  function clearLogs() {
378
- logs.length = 0;
440
+ logs.clear();
379
441
  selectedId = null;
380
442
  renderLogs();
381
- fetch('/__network-terminal__/clear', { method: 'POST' });
443
+ fetch('/clear', { method: 'POST' });
382
444
  }
383
445
 
384
446
  function addLog(log) {
385
- const existingIndex = logs.findIndex(l => l.id === log.id);
386
- if (existingIndex >= 0) {
387
- logs[existingIndex] = log;
388
- } else {
389
- logs.unshift(log);
390
- if (logs.length > 100) logs.pop();
447
+ logs.set(log.id, log);
448
+ if (logs.size > 100) {
449
+ const oldest = getLogsArray().pop();
450
+ if (oldest) logs.delete(oldest.id);
391
451
  }
392
452
  renderLogs();
393
453
  }
394
454
 
395
- // SSE connection for real-time updates
396
455
  function connect() {
397
- const evtSource = new EventSource('/__network-terminal__/events');
398
-
456
+ const evtSource = new EventSource('/events');
399
457
  evtSource.onopen = () => {
400
458
  document.getElementById('statusDot').classList.remove('disconnected');
401
- document.getElementById('statusText').textContent = 'Connected';
402
459
  };
403
-
404
460
  evtSource.onmessage = (event) => {
405
461
  const log = JSON.parse(event.data);
406
462
  addLog(log);
407
463
  };
408
-
409
464
  evtSource.onerror = () => {
410
465
  document.getElementById('statusDot').classList.add('disconnected');
411
- document.getElementById('statusText').textContent = 'Reconnecting...';
412
466
  evtSource.close();
413
467
  setTimeout(connect, 2000);
414
468
  };
@@ -422,161 +476,91 @@ const dashboardHTML = `
422
476
  `;
423
477
 
424
478
  console.log('\x1b[32m%s\x1b[0m', '\n >_ Network Terminal\n');
425
- console.log(` Proxy: http://localhost:${port} → localhost:${targetPort}`);
426
- console.log(` Dashboard: http://localhost:${dashboardPort}\n`);
427
- console.log(' Starting servers...\n');
428
-
429
- // Dashboard server
430
- const dashboardServer = http.createServer((req, res) => {
431
- if (req.url === '/__network-terminal__/events') {
432
- // SSE endpoint
479
+
480
+ const server = http.createServer((req, res) => {
481
+ // CORS headers
482
+ res.setHeader('Access-Control-Allow-Origin', '*');
483
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
484
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
485
+
486
+ if (req.method === 'OPTIONS') {
487
+ res.writeHead(204);
488
+ res.end();
489
+ return;
490
+ }
491
+
492
+ // Serve monitor script
493
+ if (req.url === '/monitor.js') {
494
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
495
+ res.end(monitorScript);
496
+ return;
497
+ }
498
+
499
+ // SSE endpoint
500
+ if (req.url === '/events') {
433
501
  res.writeHead(200, {
434
502
  'Content-Type': 'text/event-stream',
435
503
  'Cache-Control': 'no-cache',
436
504
  'Connection': 'keep-alive',
437
- 'Access-Control-Allow-Origin': '*',
438
505
  });
439
-
440
506
  sseClients.add(res);
441
-
442
- // Send existing logs
443
507
  networkLogs.forEach(log => {
444
508
  res.write(`data: ${JSON.stringify(log)}\n\n`);
445
509
  });
510
+ req.on('close', () => sseClients.delete(res));
511
+ return;
512
+ }
446
513
 
447
- req.on('close', () => {
448
- sseClients.delete(res);
514
+ // Receive logs
515
+ if (req.url === '/log' && req.method === 'POST') {
516
+ let body = '';
517
+ req.on('data', chunk => body += chunk);
518
+ req.on('end', () => {
519
+ try {
520
+ const log = JSON.parse(body);
521
+ const existingIndex = networkLogs.findIndex(l => l.id === log.id);
522
+ if (existingIndex >= 0) {
523
+ networkLogs[existingIndex] = log;
524
+ } else {
525
+ networkLogs.unshift(log);
526
+ if (networkLogs.length > 100) networkLogs.pop();
527
+ }
528
+ broadcastLog(log);
529
+ } catch (e) {}
530
+ res.writeHead(200);
531
+ res.end('OK');
449
532
  });
450
533
  return;
451
534
  }
452
535
 
453
- if (req.url === '/__network-terminal__/clear' && req.method === 'POST') {
536
+ // Clear logs
537
+ if (req.url === '/clear' && req.method === 'POST') {
454
538
  networkLogs.length = 0;
455
539
  res.writeHead(200);
456
540
  res.end('OK');
457
541
  return;
458
542
  }
459
543
 
544
+ // Serve dashboard
460
545
  res.writeHead(200, { 'Content-Type': 'text/html' });
461
546
  res.end(dashboardHTML);
462
547
  });
463
548
 
464
- // Proxy server
465
- const proxyServer = http.createServer((req, res) => {
466
- const startTime = Date.now();
467
- const currentLogId = ++logId;
468
-
469
- // Create log entry for request
470
- const log = {
471
- id: String(currentLogId),
472
- method: req.method,
473
- url: req.url,
474
- timestamp: new Date().toISOString(),
475
- requestBody: null,
476
- status: null,
477
- responseBody: null,
478
- duration: null,
479
- };
480
-
481
- // Collect request body
482
- let requestBody = '';
483
- req.on('data', chunk => requestBody += chunk);
484
- req.on('end', () => {
485
- if (requestBody) {
486
- try {
487
- log.requestBody = JSON.parse(requestBody);
488
- } catch (e) {
489
- log.requestBody = requestBody;
490
- }
491
- }
492
-
493
- // Add to logs and broadcast
494
- networkLogs.unshift(log);
495
- if (networkLogs.length > 100) networkLogs.pop();
496
- broadcastLog(log);
497
-
498
- // Forward request to target
499
- const options = {
500
- hostname: 'localhost',
501
- port: targetPort,
502
- path: req.url,
503
- method: req.method,
504
- headers: req.headers,
505
- };
549
+ server.listen(port, () => {
550
+ console.log(` Dashboard: \x1b[36mhttp://localhost:${port}\x1b[0m\n`);
551
+ console.log(' Add this to your app\'s HTML (in <head> or before </body>):\n');
552
+ console.log(` \x1b[33m<script src="http://localhost:${port}/monitor.js"></script>\x1b[0m\n`);
553
+ console.log(' Press \x1b[33mCtrl+C\x1b[0m to stop\n');
506
554
 
507
- const proxyReq = http.request(options, (proxyRes) => {
508
- const contentType = proxyRes.headers['content-type'] || '';
509
-
510
- // Collect response body
511
- let responseBody = '';
512
- proxyRes.on('data', chunk => responseBody += chunk);
513
- proxyRes.on('end', () => {
514
- // Update log with response
515
- log.status = proxyRes.statusCode;
516
- log.duration = Date.now() - startTime;
517
-
518
- if (responseBody && contentType.includes('application/json')) {
519
- try {
520
- log.responseBody = JSON.parse(responseBody);
521
- } catch (e) {
522
- log.responseBody = responseBody;
523
- }
524
- } else if (responseBody && !contentType.includes('text/html')) {
525
- log.responseBody = responseBody.substring(0, 1000);
526
- }
527
-
528
- // Broadcast updated log
529
- broadcastLog(log);
530
-
531
- // Send response to client
532
- res.writeHead(proxyRes.statusCode, proxyRes.headers);
533
- res.end(responseBody);
534
- });
535
- });
536
-
537
- proxyReq.on('error', (err) => {
538
- log.status = 502;
539
- log.duration = Date.now() - startTime;
540
- log.error = err.message;
541
- broadcastLog(log);
542
-
543
- res.writeHead(502);
544
- res.end(`Proxy Error: ${err.message}`);
545
- });
546
-
547
- if (requestBody) {
548
- proxyReq.write(requestBody);
549
- }
550
- proxyReq.end();
551
- });
555
+ // Open dashboard
556
+ const open = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
557
+ require('child_process').exec(`${open} http://localhost:${port}`);
552
558
  });
553
559
 
554
- // Start servers
555
- dashboardServer.listen(dashboardPort, () => {
556
- proxyServer.listen(port, () => {
557
- console.log('\x1b[32m%s\x1b[0m', ' Servers running!\n');
558
- console.log(` Your app: \x1b[36mhttp://localhost:${port}\x1b[0m`);
559
- console.log(` Dashboard: \x1b[36mhttp://localhost:${dashboardPort}\x1b[0m\n`);
560
- console.log(' Press \x1b[33mCtrl+C\x1b[0m to stop\n');
561
-
562
- // Open dashboard in browser
563
- const open = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
564
- require('child_process').exec(`${open} http://localhost:${dashboardPort}`);
565
- });
566
- });
567
-
568
- proxyServer.on('error', (err) => {
560
+ server.on('error', (err) => {
569
561
  if (err.code === 'EADDRINUSE') {
570
562
  console.error(`\x1b[31m Error: Port ${port} is already in use\x1b[0m`);
571
563
  process.exit(1);
572
564
  }
573
- console.error('Proxy server error:', err);
574
- });
575
-
576
- dashboardServer.on('error', (err) => {
577
- if (err.code === 'EADDRINUSE') {
578
- console.error(`\x1b[31m Error: Port ${dashboardPort} is already in use\x1b[0m`);
579
- process.exit(1);
580
- }
581
- console.error('Dashboard server error:', err);
565
+ console.error('Server error:', err);
582
566
  });