shell-mirror 1.5.63 → 1.5.65

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.
@@ -6,6 +6,7 @@ const pty = require('node-pty');
6
6
  const os = require('os');
7
7
  const { v4: uuidv4 } = require('uuid');
8
8
  const fs = require('fs');
9
+ const https = require('https');
9
10
 
10
11
  // Enhanced logging to file
11
12
  const LOG_FILE = path.join(__dirname, 'agent-debug.log');
@@ -410,12 +411,92 @@ const iceServers = [
410
411
  }
411
412
  ];
412
413
 
414
+ // --- Heartbeat System ---
415
+ let heartbeatInterval;
416
+
417
+ async function sendHeartbeat() {
418
+ try {
419
+ const heartbeatData = JSON.stringify({
420
+ agentId: AGENT_ID,
421
+ timestamp: Date.now(),
422
+ activeSessions: Object.keys(sessions).length
423
+ });
424
+
425
+ const options = {
426
+ hostname: 'shellmirror.app',
427
+ port: 443,
428
+ path: '/php-backend/api/agent-heartbeat.php',
429
+ method: 'POST',
430
+ headers: {
431
+ 'Content-Type': 'application/json',
432
+ 'Content-Length': Buffer.byteLength(heartbeatData),
433
+ 'X-Agent-Secret': 'mac-agent-secret-2024',
434
+ 'X-Agent-ID': AGENT_ID
435
+ }
436
+ };
437
+
438
+ const req = https.request(options, (res) => {
439
+ let responseData = '';
440
+ res.on('data', (chunk) => {
441
+ responseData += chunk;
442
+ });
443
+ res.on('end', () => {
444
+ if (res.statusCode === 200) {
445
+ try {
446
+ const result = JSON.parse(responseData);
447
+ if (result.success) {
448
+ logToFile(`💓 Heartbeat sent successfully`);
449
+ } else {
450
+ logToFile(`⚠️ Heartbeat failed: ${result.message}`);
451
+ }
452
+ } catch (error) {
453
+ logToFile(`⚠️ Heartbeat response parse error: ${error.message}`);
454
+ }
455
+ } else {
456
+ logToFile(`⚠️ Heartbeat HTTP error: ${res.statusCode}`);
457
+ }
458
+ });
459
+ });
460
+
461
+ req.on('error', (error) => {
462
+ logToFile(`❌ Heartbeat request failed: ${error.message}`);
463
+ });
464
+
465
+ req.write(heartbeatData);
466
+ req.end();
467
+
468
+ } catch (error) {
469
+ logToFile(`❌ Heartbeat error: ${error.message}`);
470
+ }
471
+ }
472
+
473
+ function startHeartbeatSystem() {
474
+ logToFile('💓 Starting heartbeat system (60 second interval)');
475
+
476
+ // Send initial heartbeat immediately
477
+ sendHeartbeat();
478
+
479
+ // Set up recurring heartbeat
480
+ heartbeatInterval = setInterval(sendHeartbeat, 60000); // 60 seconds
481
+ }
482
+
483
+ function stopHeartbeatSystem() {
484
+ if (heartbeatInterval) {
485
+ clearInterval(heartbeatInterval);
486
+ heartbeatInterval = null;
487
+ logToFile('💓 Heartbeat system stopped');
488
+ }
489
+ }
490
+
413
491
  function connectToSignalingServer() {
414
492
  logToFile(`🔌 Connecting to signaling server at ${SIGNALING_SERVER_URL}?role=agent&agentId=${AGENT_ID}`);
415
493
  ws = new WebSocket(`${SIGNALING_SERVER_URL}?role=agent&agentId=${AGENT_ID}`);
416
494
 
417
495
  ws.on('open', () => {
418
496
  logToFile('✅ Connected to signaling server.');
497
+
498
+ // Start heartbeat system to maintain agent status
499
+ startHeartbeatSystem();
419
500
  });
420
501
 
421
502
  ws.on('message', async (message) => {
@@ -689,6 +770,9 @@ async function createPeerConnection(clientId) {
689
770
  }
690
771
 
691
772
  function cleanup(clientId = null) {
773
+ // Stop heartbeat system
774
+ stopHeartbeatSystem();
775
+
692
776
  // Disconnect client from session manager
693
777
  if (clientId) {
694
778
  sessionManager.disconnectClient(clientId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.63",
3
+ "version": "1.5.65",
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": {
@@ -530,22 +530,12 @@ class ShellMirrorDashboard {
530
530
  }
531
531
 
532
532
  renderActiveAgents() {
533
- // Filter for agents that should be displayed (online, recent, or offline with sessions)
533
+ // Filter for agents that should be displayed (only online/recent - offline agents can't be recovered)
534
534
  const displayAgents = this.agents.filter(agent => {
535
- // Always show online agents
536
- if (agent.status === 'online') return true;
537
-
538
- // Show recent agents (last 2 minutes)
539
- if (agent.status === 'recent') return true;
540
-
541
- // Show offline agents only if they have active sessions
542
- const sessions = this.agentSessions[agent.agentId] || [];
543
- const activeSessions = sessions.filter(s => s.status === 'active');
544
- return activeSessions.length > 0;
535
+ return agent.status === 'online' || agent.status === 'recent';
545
536
  });
546
537
 
547
- // Separate truly active vs inactive agents
548
- const activeAgents = displayAgents.filter(agent => agent.status === 'online' || agent.status === 'recent');
538
+ // All displayed agents are now connectable (online/recent only)
549
539
 
550
540
  const agentCount = displayAgents.length;
551
541
  const agentsHtml = displayAgents.map(agent => {