shell-mirror 1.5.125 → 1.5.127

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.
@@ -807,15 +807,79 @@ async function createPeerConnection(clientId) {
807
807
  };
808
808
  }
809
809
 
810
- function cleanup(clientId = null) {
810
+ async function cleanup(clientId = null) {
811
811
  // Disconnect client from session manager
812
812
  if (clientId) {
813
813
  sessionManager.disconnectClient(clientId);
814
814
  } else {
815
- // Full agent shutdown - stop heartbeat system
815
+ // Full agent shutdown - send final heartbeat with empty sessions BEFORE stopping heartbeat
816
+ logToFile('[AGENT] Sending final heartbeat with empty sessions before shutdown...');
817
+
818
+ if (heartbeatInterval) {
819
+ const finalHeartbeatData = JSON.stringify({
820
+ agentId: AGENT_ID,
821
+ timestamp: Date.now(),
822
+ activeSessions: 0,
823
+ sessions: [], // Empty session list to clear dashboard
824
+ localPort: process.env.LOCAL_PORT || 8080,
825
+ capabilities: ['webrtc', 'direct_websocket'],
826
+ status: 'shutting_down'
827
+ });
828
+
829
+ const options = {
830
+ hostname: 'shellmirror.app',
831
+ port: 443,
832
+ path: '/php-backend/api/agent-heartbeat.php',
833
+ method: 'POST',
834
+ headers: {
835
+ 'Content-Type': 'application/json',
836
+ 'Content-Length': Buffer.byteLength(finalHeartbeatData),
837
+ 'X-Agent-Secret': 'mac-agent-secret-2024',
838
+ 'X-Agent-ID': AGENT_ID
839
+ }
840
+ };
841
+
842
+ try {
843
+ await new Promise((resolve, reject) => {
844
+ const req = https.request(options, (res) => {
845
+ let responseData = '';
846
+ res.on('data', (chunk) => {
847
+ responseData += chunk;
848
+ });
849
+ res.on('end', () => {
850
+ if (res.statusCode === 200) {
851
+ logToFile('[AGENT] ✅ Sent final heartbeat with empty sessions');
852
+ resolve();
853
+ } else {
854
+ logToFile(`[AGENT] ⚠️ Final heartbeat HTTP error: ${res.statusCode}`);
855
+ resolve(); // Continue shutdown even if heartbeat fails
856
+ }
857
+ });
858
+ });
859
+
860
+ req.on('error', (error) => {
861
+ logToFile(`[AGENT] ❌ Failed to send final heartbeat: ${error.message}`);
862
+ resolve(); // Continue shutdown even if heartbeat fails
863
+ });
864
+
865
+ req.setTimeout(5000, () => {
866
+ req.destroy();
867
+ logToFile('[AGENT] ⚠️ Final heartbeat timed out after 5s');
868
+ resolve(); // Continue shutdown even if heartbeat times out
869
+ });
870
+
871
+ req.write(finalHeartbeatData);
872
+ req.end();
873
+ });
874
+ } catch (error) {
875
+ logToFile(`[AGENT] ❌ Error sending final heartbeat: ${error.message}`);
876
+ }
877
+ }
878
+
879
+ // Now stop heartbeat system
816
880
  stopHeartbeatSystem();
817
881
  }
818
-
882
+
819
883
  if (dataChannel) {
820
884
  dataChannel.close();
821
885
  dataChannel = null;
@@ -1058,17 +1122,17 @@ function sendMessage(message) {
1058
1122
  }
1059
1123
 
1060
1124
  // Graceful shutdown handling
1061
- process.on('SIGINT', () => {
1125
+ process.on('SIGINT', async () => {
1062
1126
  console.log('\n[AGENT] Shutting down gracefully...');
1063
- cleanup();
1127
+ await cleanup();
1064
1128
  if (ws) ws.close();
1065
1129
  if (localServer) localServer.close();
1066
1130
  process.exit(0);
1067
1131
  });
1068
1132
 
1069
- process.on('SIGTERM', () => {
1133
+ process.on('SIGTERM', async () => {
1070
1134
  console.log('\n[AGENT] Received SIGTERM, shutting down...');
1071
- cleanup();
1135
+ await cleanup();
1072
1136
  if (ws) ws.close();
1073
1137
  if (localServer) localServer.close();
1074
1138
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.125",
3
+ "version": "1.5.127",
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": {
@@ -231,6 +231,17 @@
231
231
  50% { opacity: 0.5; }
232
232
  }
233
233
 
234
+ @keyframes calm-pulsate {
235
+ 0%, 100% {
236
+ box-shadow: 0 0 0 rgba(102, 126, 234, 0);
237
+ border-color: #444;
238
+ }
239
+ 50% {
240
+ box-shadow: 0 0 12px rgba(102, 126, 234, 0.6);
241
+ border-color: #667eea;
242
+ }
243
+ }
244
+
234
245
  /* Header Buttons */
235
246
  .header-btn {
236
247
  background: transparent;
@@ -252,6 +263,10 @@
252
263
  border-color: #667eea;
253
264
  }
254
265
 
266
+ .dashboard-btn.pulsate {
267
+ animation: calm-pulsate 2s ease-in-out infinite;
268
+ }
269
+
255
270
  .help-btn .btn-text {
256
271
  display: none;
257
272
  }
@@ -189,6 +189,7 @@ function clearConnectionTimeouts() {
189
189
  if (connectionTimeoutWarning) {
190
190
  clearTimeout(connectionTimeoutWarning.timeout10s);
191
191
  clearTimeout(connectionTimeoutWarning.timeout30s);
192
+ clearTimeout(connectionTimeoutWarning.timeout60s);
192
193
  connectionTimeoutWarning = null;
193
194
  console.log('[CLIENT] ✅ Connection timeout warnings cleared');
194
195
  }
@@ -310,8 +311,28 @@ function startConnection() {
310
311
  }
311
312
  }, 30000);
312
313
 
314
+ const timeout60s = setTimeout(() => {
315
+ if (!currentSession) {
316
+ updateConnectionStatus('disconnected');
317
+ setConnectionMessage('❌ Failed to connect - Agent or session unavailable', false);
318
+ term.write('\r\n\r\n\x1b[31m❌ Connection Failed\x1b[0m\r\n');
319
+ term.write('\x1b[33mThe agent or session you requested is not available.\x1b[0m\r\n');
320
+ term.write('\r\n\x1b[36mPossible reasons:\x1b[0m\r\n');
321
+ term.write(' • Agent is offline or shut down\r\n');
322
+ term.write(' • Session was terminated\r\n');
323
+ term.write(' • Network connectivity issues\r\n');
324
+ term.write('\r\n\x1b[36m💡 Click Dashboard to return and try another session\x1b[0m\r\n');
325
+
326
+ // Make Dashboard button pulsate calmly to guide user back
327
+ const dashboardBtn = document.querySelector('.dashboard-btn');
328
+ if (dashboardBtn) {
329
+ dashboardBtn.classList.add('pulsate');
330
+ }
331
+ }
332
+ }, 60000);
333
+
313
334
  // Store timeout IDs so they can be cleared on successful connection
314
- connectionTimeoutWarning = { timeout10s, timeout30s };
335
+ connectionTimeoutWarning = { timeout10s, timeout30s, timeout60s };
315
336
 
316
337
  initialize();
317
338
  }