shell-mirror 1.5.56 → 1.5.58

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": "shell-mirror",
3
- "version": "1.5.56",
3
+ "version": "1.5.58",
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": {
@@ -152,6 +152,37 @@ body {
152
152
  animation: spin 1s linear infinite;
153
153
  }
154
154
 
155
+ /* Inline Refresh Button */
156
+ .refresh-btn-inline {
157
+ background: #f8f9fa;
158
+ border: 1px solid #e9ecef;
159
+ border-radius: 50%;
160
+ color: #666;
161
+ width: 32px;
162
+ height: 32px;
163
+ cursor: pointer;
164
+ transition: all 0.2s ease;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ font-size: 0.9rem;
169
+ }
170
+
171
+ .refresh-btn-inline:hover:not(:disabled) {
172
+ background: #e9ecef;
173
+ color: #333;
174
+ transform: scale(1.05);
175
+ }
176
+
177
+ .refresh-btn-inline.loading {
178
+ opacity: 0.7;
179
+ cursor: not-allowed;
180
+ }
181
+
182
+ .refresh-btn-inline.loading .refresh-icon {
183
+ animation: spin 1s linear infinite;
184
+ }
185
+
155
186
  .connection-status {
156
187
  font-size: 0.8rem;
157
188
  font-weight: 500;
@@ -242,6 +273,12 @@ body {
242
273
  margin-bottom: 20px;
243
274
  }
244
275
 
276
+ .card-title-section {
277
+ display: flex;
278
+ align-items: center;
279
+ gap: 12px;
280
+ }
281
+
245
282
  .card-header h2 {
246
283
  font-size: 1.3rem;
247
284
  font-weight: 600;
@@ -833,4 +870,23 @@ body {
833
870
  opacity: 0;
834
871
  transform: translateX(100%);
835
872
  }
873
+ }
874
+
875
+ /* API Error Display */
876
+ .api-error {
877
+ text-align: center;
878
+ padding: 20px;
879
+ background: #fff5f5;
880
+ border: 1px solid #fed7d7;
881
+ border-radius: 8px;
882
+ color: #c53030;
883
+ }
884
+
885
+ .api-error p {
886
+ margin-bottom: 15px;
887
+ font-weight: 500;
888
+ }
889
+
890
+ .api-error button {
891
+ margin-top: 10px;
836
892
  }
@@ -12,6 +12,8 @@ class ShellMirrorDashboard {
12
12
  this.refreshInterval = null;
13
13
  this.lastRefresh = null;
14
14
  this.isRefreshing = false;
15
+ this.connectionStatusDebounce = null;
16
+ this.currentConnectionStatus = null;
15
17
  this.init();
16
18
  }
17
19
 
@@ -102,8 +104,24 @@ class ShellMirrorDashboard {
102
104
  }
103
105
 
104
106
  setupWebSocket() {
107
+ // Detect production environment
108
+ const isProduction = window.location.hostname === 'shellmirror.app' ||
109
+ window.location.hostname === 'www.shellmirror.app' ||
110
+ window.location.hostname === 'www.igori.eu' ||
111
+ window.location.hostname === 'igori.eu';
112
+
105
113
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
106
- const wsUrl = `${protocol}//${window.location.host}/?role=dashboard`;
114
+ let wsUrl;
115
+
116
+ if (isProduction) {
117
+ // For production, try the Heroku WebSocket app URL
118
+ // This may need to be adjusted based on actual deployment architecture
119
+ wsUrl = `wss://shell-mirror-30aa5479ceaf.herokuapp.com/?role=dashboard`;
120
+ console.log('[DASHBOARD] 🌐 Production environment detected, using Heroku WebSocket URL');
121
+ } else {
122
+ // For development, use same host
123
+ wsUrl = `${protocol}//${window.location.host}/?role=dashboard`;
124
+ }
107
125
 
108
126
  console.log('[DASHBOARD] 🔌 Connecting to WebSocket:', wsUrl);
109
127
 
@@ -111,9 +129,20 @@ class ShellMirrorDashboard {
111
129
  this.websocket = new WebSocket(wsUrl);
112
130
 
113
131
  this.websocket.onopen = () => {
114
- console.log('[DASHBOARD] ✅ WebSocket connected');
132
+ console.log('[DASHBOARD] ✅ WebSocket connected to:', wsUrl);
115
133
  this.reconnectAttempts = 0;
116
134
  this.updateConnectionStatus('connected');
135
+
136
+ // Send authentication info if available
137
+ const user = this.user;
138
+ if (user) {
139
+ console.log('[DASHBOARD] 🔐 Sending authentication to WebSocket');
140
+ this.websocket.send(JSON.stringify({
141
+ type: 'authenticate',
142
+ userId: user.id || user.email,
143
+ email: user.email
144
+ }));
145
+ }
117
146
  };
118
147
 
119
148
  this.websocket.onmessage = (event) => {
@@ -121,13 +150,38 @@ class ShellMirrorDashboard {
121
150
  };
122
151
 
123
152
  this.websocket.onclose = (event) => {
124
- console.log('[DASHBOARD] 🔌 WebSocket closed:', event.code, event.reason);
153
+ const closeReasons = {
154
+ 1000: 'Normal Closure',
155
+ 1001: 'Going Away',
156
+ 1002: 'Protocol Error',
157
+ 1003: 'Unsupported Data',
158
+ 1004: 'Reserved',
159
+ 1005: 'No Status Rcvd',
160
+ 1006: 'Abnormal Closure',
161
+ 1007: 'Invalid frame payload data',
162
+ 1008: 'Policy Violation',
163
+ 1009: 'Message too big',
164
+ 1010: 'Mandatory Extension',
165
+ 1011: 'Internal Server Error',
166
+ 1015: 'TLS Handshake'
167
+ };
168
+
169
+ const reason = closeReasons[event.code] || 'Unknown';
170
+ console.log(`[DASHBOARD] 🔌 WebSocket closed: ${event.code} (${reason})`, event.reason);
171
+
172
+ if (event.code === 1008) {
173
+ console.error('[DASHBOARD] ❌ Authentication required - WebSocket rejected connection');
174
+ } else if (event.code === 1006) {
175
+ console.error('[DASHBOARD] ❌ Abnormal closure - WebSocket endpoint may not exist');
176
+ }
177
+
125
178
  this.updateConnectionStatus('disconnected');
126
179
  this.attemptReconnect();
127
180
  };
128
181
 
129
182
  this.websocket.onerror = (error) => {
130
183
  console.error('[DASHBOARD] ❌ WebSocket error:', error);
184
+ console.log('[DASHBOARD] 🔍 WebSocket URL attempted:', wsUrl);
131
185
  this.updateConnectionStatus('error');
132
186
  };
133
187
 
@@ -222,22 +276,74 @@ class ShellMirrorDashboard {
222
276
  this.setupWebSocket();
223
277
  }, delay);
224
278
  } else {
225
- console.log('[DASHBOARD] ❌ Max reconnection attempts reached or not authenticated');
226
- this.updateConnectionStatus('failed');
279
+ console.log('[DASHBOARD] ❌ Max reconnection attempts reached, switching to HTTP-only mode');
280
+ this.updateConnectionStatus('offline');
281
+ this.enableHttpOnlyMode();
227
282
  }
228
283
  }
229
284
 
285
+ enableHttpOnlyMode() {
286
+ console.log('[DASHBOARD] 📡 Enabling HTTP-only mode (no real-time updates)');
287
+ this.websocket = null;
288
+
289
+ // Update UI to show HTTP-only mode
290
+ const connectionStatus = document.getElementById('connection-status');
291
+ if (connectionStatus) {
292
+ connectionStatus.textContent = '📡 HTTP Only';
293
+ connectionStatus.className = 'connection-status offline';
294
+ connectionStatus.title = 'Real-time updates unavailable - using polling';
295
+ }
296
+
297
+ // Continue with HTTP polling only
298
+ console.log('[DASHBOARD] ✅ Dashboard running in HTTP-only mode');
299
+ }
300
+
230
301
  updateConnectionStatus(status) {
302
+ // Debounce rapid status changes to prevent UI flickering
303
+ if (this.connectionStatusDebounce) {
304
+ clearTimeout(this.connectionStatusDebounce);
305
+ }
306
+
307
+ // Don't update if status is the same
308
+ if (this.currentConnectionStatus === status) {
309
+ return;
310
+ }
311
+
312
+ // For rapid disconnection/reconnection, only show status after a delay
313
+ if (status === 'disconnected' && this.currentConnectionStatus === 'connected') {
314
+ this.connectionStatusDebounce = setTimeout(() => {
315
+ this.setConnectionStatusUI(status);
316
+ }, 2000); // Wait 2 seconds before showing disconnected
317
+ return;
318
+ }
319
+
320
+ // For reconnection, show immediately but don't flicker
321
+ if (status === 'connected' && this.connectionStatusDebounce) {
322
+ clearTimeout(this.connectionStatusDebounce);
323
+ this.connectionStatusDebounce = null;
324
+ }
325
+
326
+ this.setConnectionStatusUI(status);
327
+ }
328
+
329
+ setConnectionStatusUI(status) {
330
+ this.currentConnectionStatus = status;
231
331
  const connectionStatus = document.getElementById('connection-status');
232
332
  if (connectionStatus) {
233
333
  connectionStatus.className = `connection-status ${status}`;
234
- const statusText = {
235
- connected: '🟢 Live',
236
- disconnected: '🟡 Reconnecting...',
237
- error: '🔴 Connection Error',
238
- failed: '🔴 Offline'
334
+
335
+ // Only show status when there are issues or special modes
336
+ const statusConfig = {
337
+ connected: { text: '', show: false }, // Hide when everything is working
338
+ disconnected: { text: '🟡 Reconnecting...', show: true },
339
+ error: { text: '🔴 Connection Error', show: true },
340
+ failed: { text: '🔴 Offline', show: true },
341
+ offline: { text: '📡 HTTP Only', show: true }
239
342
  };
240
- connectionStatus.textContent = statusText[status] || '❓ Unknown';
343
+
344
+ const config = statusConfig[status] || { text: '❓ Unknown', show: true };
345
+ connectionStatus.textContent = config.text;
346
+ connectionStatus.style.display = config.show ? 'inline-block' : 'none';
241
347
  }
242
348
  }
243
349
 
@@ -301,15 +407,27 @@ class ShellMirrorDashboard {
301
407
 
302
408
  async loadDashboardData() {
303
409
  try {
304
- // Load active agents
305
- const agentsResponse = await fetch('/php-backend/api/agents-list.php');
410
+ // Load active agents with detailed debugging
411
+ console.log('[DASHBOARD] 📡 Fetching agents from API...');
412
+ const agentsResponse = await fetch('/php-backend/api/agents-list.php', {
413
+ credentials: 'include' // Include authentication cookies
414
+ });
415
+
416
+ console.log('[DASHBOARD] 🔍 API Response Status:', agentsResponse.status);
417
+ console.log('[DASHBOARD] 🔍 API Response Headers:', Object.fromEntries(agentsResponse.headers.entries()));
418
+
306
419
  const agentsData = await agentsResponse.json();
420
+ console.log('[DASHBOARD] 🔍 API Response Data:', agentsData);
307
421
 
308
422
  if (agentsData.success && agentsData.data && agentsData.data.agents) {
309
423
  this.agents = agentsData.data.agents;
424
+ console.log('[DASHBOARD] ✅ Loaded agents:', this.agents.length);
310
425
 
311
426
  // Load session data from localStorage (persisted from terminal connections)
312
427
  this.loadSessionsFromStorage();
428
+ } else {
429
+ console.warn('[DASHBOARD] ⚠️ No agents found in API response:', agentsData);
430
+ this.agents = [];
313
431
  }
314
432
 
315
433
  // TODO: Load session history when API is available
@@ -331,7 +449,25 @@ class ShellMirrorDashboard {
331
449
  ];
332
450
 
333
451
  } catch (error) {
334
- console.error('Failed to load dashboard data:', error);
452
+ console.error('[DASHBOARD] ❌ Failed to load dashboard data:', error);
453
+ this.agents = [];
454
+
455
+ // Show error in UI
456
+ const agentsCard = document.querySelector('.dashboard-card');
457
+ if (agentsCard) {
458
+ agentsCard.innerHTML = `
459
+ <div class="card-header">
460
+ <h2>🖥️ Active Agents</h2>
461
+ <span class="agent-count">Error</span>
462
+ </div>
463
+ <div class="card-content">
464
+ <div class="api-error">
465
+ <p>⚠️ Failed to load agents: ${error.message}</p>
466
+ <button onclick="dashboard.manualRefresh()" class="btn-primary">Retry</button>
467
+ </div>
468
+ </div>
469
+ `;
470
+ }
335
471
  }
336
472
  }
337
473
 
@@ -366,14 +502,11 @@ class ShellMirrorDashboard {
366
502
  }
367
503
 
368
504
  renderAuthenticatedDashboard() {
369
- // Update user section with refresh button and status
505
+ // Update user section - simplified without refresh button
370
506
  document.getElementById('user-section').innerHTML = `
371
507
  <div class="dashboard-controls">
372
- <span id="connection-status" class="connection-status">🟡 Connecting...</span>
508
+ <span id="connection-status" class="connection-status" style="display: none;"></span>
373
509
  <span id="refresh-status" class="refresh-status">Initializing...</span>
374
- <button id="refresh-btn" class="refresh-btn" onclick="dashboard.manualRefresh()" title="Refresh agents">
375
- <span class="refresh-icon">🔄</span>
376
- </button>
377
510
  </div>
378
511
  <div class="user-info">
379
512
  <span class="user-name">${this.user.name || this.user.email}</span>
@@ -438,8 +571,13 @@ class ShellMirrorDashboard {
438
571
  return `
439
572
  <div class="dashboard-card">
440
573
  <div class="card-header">
441
- <h2>🖥️ Active Agents</h2>
442
- <span class="agent-count">${agentCount} agent${agentCount !== 1 ? 's' : ''}</span>
574
+ <div class="card-title-section">
575
+ <h2>🖥️ Active Agents</h2>
576
+ <span class="agent-count">${agentCount} agent${agentCount !== 1 ? 's' : ''}</span>
577
+ </div>
578
+ <button id="refresh-btn" class="refresh-btn-inline" onclick="dashboard.manualRefresh()" title="Refresh agents">
579
+ <span class="refresh-icon">🔄</span>
580
+ </button>
443
581
  </div>
444
582
  <div class="card-content">
445
583
  ${agentCount > 0 ? agentsHtml : '<p class="no-data">No active agents. <a href="#" onclick="dashboard.showAgentInstructions()">Set up an agent</a></p>'}