shell-mirror 1.5.39 → 1.5.40

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.
@@ -212,6 +212,11 @@ async function createPeerConnection(clientId) {
212
212
  };
213
213
 
214
214
  peerConnection.oniceconnectionstatechange = () => {
215
+ if (!peerConnection) {
216
+ logToFile('[AGENT] ⚠️ ICE connection state change after peerConnection was closed');
217
+ return;
218
+ }
219
+
215
220
  logToFile(`[AGENT] 📊 ICE connection state changed: ${peerConnection.iceConnectionState}`);
216
221
  logToFile(`[AGENT] 📊 ICE gathering state: ${peerConnection.iceGatheringState}`);
217
222
 
@@ -243,6 +248,11 @@ async function createPeerConnection(clientId) {
243
248
  };
244
249
 
245
250
  peerConnection.onconnectionstatechange = () => {
251
+ if (!peerConnection) {
252
+ logToFile('[AGENT] ⚠️ Connection state change after peerConnection was closed');
253
+ return;
254
+ }
255
+
246
256
  logToFile(`[AGENT] 📡 Connection state changed: ${peerConnection.connectionState}`);
247
257
 
248
258
  switch (peerConnection.connectionState) {
@@ -268,6 +278,11 @@ async function createPeerConnection(clientId) {
268
278
  };
269
279
 
270
280
  peerConnection.onicegatheringstatechange = () => {
281
+ if (!peerConnection) {
282
+ logToFile('[AGENT] ⚠️ ICE gathering state change after peerConnection was closed');
283
+ return;
284
+ }
285
+
271
286
  logToFile(`[AGENT] 🔍 ICE gathering state changed: ${peerConnection.iceGatheringState}`);
272
287
 
273
288
  switch (peerConnection.iceGatheringState) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.39",
3
+ "version": "1.5.40",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -20,6 +20,7 @@ class ShellMirrorDashboard {
20
20
  this.user = authStatus.user;
21
21
  await this.loadDashboardData();
22
22
  this.renderAuthenticatedDashboard();
23
+ this.startAutoRefresh(); // Start auto-refresh for authenticated users
23
24
  } else {
24
25
  this.renderUnauthenticatedDashboard();
25
26
  }
@@ -31,6 +32,24 @@ class ShellMirrorDashboard {
31
32
  }
32
33
  }
33
34
 
35
+ startAutoRefresh() {
36
+ // Refresh agent data every 30 seconds to detect disconnected agents
37
+ this.refreshInterval = setInterval(async () => {
38
+ if (this.isAuthenticated) {
39
+ await this.loadDashboardData();
40
+ // Only re-render the agents section to avoid full page flash
41
+ this.updateAgentsDisplay();
42
+ }
43
+ }, 30000);
44
+ }
45
+
46
+ updateAgentsDisplay() {
47
+ const agentsCard = document.querySelector('.dashboard-card');
48
+ if (agentsCard) {
49
+ agentsCard.innerHTML = this.renderActiveAgents();
50
+ }
51
+ }
52
+
34
53
  showLoading() {
35
54
  document.getElementById('loading-overlay').style.display = 'flex';
36
55
  }
@@ -35,7 +35,7 @@
35
35
  .back-to-dashboard {
36
36
  position: fixed;
37
37
  top: 20px;
38
- left: 20px;
38
+ right: 20px;
39
39
  background: rgba(66, 133, 244, 0.9);
40
40
  color: white;
41
41
  border: none;
@@ -55,11 +55,37 @@
55
55
  background: rgba(51, 103, 214, 0.9);
56
56
  transform: translateY(-1px);
57
57
  }
58
+
59
+ /* Connection Status Indicator */
60
+ .connection-status {
61
+ width: 8px;
62
+ height: 8px;
63
+ border-radius: 50%;
64
+ background: #ff4444;
65
+ margin-right: 4px;
66
+ transition: all 0.3s ease;
67
+ }
68
+
69
+ .connection-status.connected {
70
+ background: #44ff44;
71
+ box-shadow: 0 0 8px rgba(68, 255, 68, 0.5);
72
+ }
73
+
74
+ .connection-status.connecting {
75
+ background: #ffaa44;
76
+ animation: pulse 1.5s ease-in-out infinite alternate;
77
+ }
78
+
79
+ @keyframes pulse {
80
+ from { opacity: 1; }
81
+ to { opacity: 0.4; }
82
+ }
58
83
  </style>
59
84
  </head>
60
85
  <body>
61
86
  <!-- Back to Dashboard Button -->
62
- <a href="/app/dashboard.html" class="back-to-dashboard">
87
+ <a href="/app/dashboard.html" class="back-to-dashboard" id="dashboard-btn">
88
+ <div class="connection-status" id="connection-status"></div>
63
89
  <span>←</span>
64
90
  <span>Dashboard</span>
65
91
  </a>
@@ -50,6 +50,26 @@ let AGENT_ID;
50
50
  let CLIENT_ID;
51
51
  let SELECTED_AGENT; // Store full agent data including WebSocket URL
52
52
 
53
+ // Connection status management
54
+ function updateConnectionStatus(status) {
55
+ const statusElement = document.getElementById('connection-status');
56
+ if (!statusElement) return;
57
+
58
+ statusElement.className = 'connection-status';
59
+ switch(status) {
60
+ case 'connecting':
61
+ statusElement.classList.add('connecting');
62
+ break;
63
+ case 'connected':
64
+ statusElement.classList.add('connected');
65
+ break;
66
+ case 'disconnected':
67
+ default:
68
+ // Default red styling already applied
69
+ break;
70
+ }
71
+ }
72
+
53
73
  // Check for agent parameter and connect directly
54
74
  window.addEventListener('load', () => {
55
75
  loadVersionInfo();
@@ -97,6 +117,7 @@ async function loadVersionInfo() {
97
117
 
98
118
 
99
119
  function startConnection() {
120
+ updateConnectionStatus('connecting');
100
121
  connectContainer.style.display = 'none';
101
122
  terminalContainer.style.display = 'block';
102
123
  term.open(document.getElementById('terminal'));
@@ -346,25 +367,31 @@ async function createPeerConnection() {
346
367
  break;
347
368
  case 'connected':
348
369
  console.log('[CLIENT] ✅ WebRTC connection established!');
370
+ updateConnectionStatus('connected');
349
371
  break;
350
372
  case 'completed':
351
373
  console.log('[CLIENT] ✅ ICE connection completed successfully!');
374
+ updateConnectionStatus('connected');
352
375
  break;
353
376
  case 'failed':
354
377
  console.log('[CLIENT] ❌ ICE connection failed - no viable candidates');
355
378
  console.log('[CLIENT] 💡 Troubleshooting: This may be due to firewall/NAT issues or blocked STUN servers');
379
+ updateConnectionStatus('disconnected');
356
380
  term.write('\r\n\r\n❌ Connection failed: Network connectivity issues\r\n');
357
381
  term.write('💡 This may be due to:\r\n');
358
382
  term.write(' • Firewall blocking WebRTC traffic\r\n');
359
383
  term.write(' • Corporate network restrictions\r\n');
360
384
  term.write(' • STUN/TURN servers unreachable\r\n');
361
- term.write('\r\n🔄 Please refresh the page to retry...\r\n');
385
+ term.write(' Agent may have crashed or disconnected\r\n');
386
+ term.write('\r\n🔄 Click Dashboard to return and try another agent\r\n');
362
387
  break;
363
388
  case 'disconnected':
364
389
  console.log('[CLIENT] ⚠️ ICE connection disconnected');
390
+ updateConnectionStatus('disconnected');
365
391
  break;
366
392
  case 'closed':
367
393
  console.log('[CLIENT] 🔐 ICE connection closed');
394
+ updateConnectionStatus('disconnected');
368
395
  break;
369
396
  }
370
397
  };
@@ -440,12 +467,16 @@ function setupDataChannel() {
440
467
 
441
468
  dataChannel.onclose = () => {
442
469
  console.log('[CLIENT] Data channel closed.');
470
+ updateConnectionStatus('disconnected');
443
471
  term.write('\r\n\r\n\x1b[31m❌ Terminal session ended.\x1b[0m\r\n');
472
+ term.write('🔄 Click Dashboard to return and start a new session\r\n');
444
473
  };
445
474
 
446
475
  dataChannel.onerror = (error) => {
447
476
  console.error('[CLIENT] Data channel error:', error);
477
+ updateConnectionStatus('disconnected');
448
478
  term.write('\r\n\r\n\x1b[31m❌ Data channel error occurred.\x1b[0m\r\n');
479
+ term.write('🔄 Click Dashboard to return and try again\r\n');
449
480
  };
450
481
 
451
482
  term.onData((data) => {