shell-mirror 1.5.16 → 1.5.18
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/lib/auto-start.js +79 -42
- package/lib/network-utils.js +145 -0
- package/package.json +1 -1
- package/public/app/terminal.html +1 -1
- package/public/app/terminal.js +36 -7
- package/public/index.html +9 -1
- package/server.js +8 -3
package/lib/auto-start.js
CHANGED
|
@@ -5,6 +5,7 @@ const crypto = require('crypto');
|
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
6
|
const https = require('https');
|
|
7
7
|
const querystring = require('querystring');
|
|
8
|
+
const NetworkUtils = require('./network-utils');
|
|
8
9
|
|
|
9
10
|
class AutoStart {
|
|
10
11
|
constructor() {
|
|
@@ -400,13 +401,16 @@ NODE_ENV=development
|
|
|
400
401
|
});
|
|
401
402
|
}
|
|
402
403
|
|
|
403
|
-
async createEnvFile(authInfo) {
|
|
404
|
+
async createEnvFile(authInfo, wsConfig = null) {
|
|
405
|
+
const port = wsConfig ? wsConfig.port : 8080;
|
|
406
|
+
const host = wsConfig ? wsConfig.host : '0.0.0.0';
|
|
407
|
+
|
|
404
408
|
const envContent = `# Shell Mirror Configuration
|
|
405
409
|
# Auto-generated on ${new Date().toISOString()}
|
|
406
410
|
|
|
407
|
-
BASE_URL=http://localhost
|
|
408
|
-
PORT
|
|
409
|
-
HOST
|
|
411
|
+
BASE_URL=http://localhost:${port}
|
|
412
|
+
PORT=${port}
|
|
413
|
+
HOST=${host}
|
|
410
414
|
GOOGLE_CLIENT_ID=${this.oauthConfig.clientId}
|
|
411
415
|
GOOGLE_CLIENT_SECRET=${this.oauthConfig.clientSecret}
|
|
412
416
|
SESSION_SECRET=${crypto.randomBytes(32).toString('hex')}
|
|
@@ -416,62 +420,93 @@ NODE_ENV=development
|
|
|
416
420
|
USER_EMAIL=${authInfo.email}
|
|
417
421
|
USER_NAME=${authInfo.name}
|
|
418
422
|
ACCESS_TOKEN=${authInfo.accessToken}
|
|
423
|
+
|
|
424
|
+
# WebSocket server configuration
|
|
425
|
+
${wsConfig ? `WS_PORT=${wsConfig.port}
|
|
426
|
+
WS_HOST=${wsConfig.host}
|
|
427
|
+
WS_LOCAL_IP=${wsConfig.localIP}
|
|
428
|
+
WS_URL=${wsConfig.wsUrl}` : ''}
|
|
419
429
|
`;
|
|
420
430
|
|
|
421
431
|
await fs.writeFile(this.envFile, envContent);
|
|
422
432
|
}
|
|
423
433
|
|
|
424
|
-
displayStatus(authInfo) {
|
|
434
|
+
displayStatus(authInfo, wsConfig = null) {
|
|
425
435
|
const pkg = require('../package.json');
|
|
436
|
+
console.log('');
|
|
426
437
|
console.log(`✅ Shell Mirror v${pkg.version} - Running (${authInfo.email})`);
|
|
438
|
+
if (wsConfig) {
|
|
439
|
+
console.log(`🌐 WebSocket Server: ${wsConfig.wsUrl}`);
|
|
440
|
+
console.log(`📱 Access from browser: https://shellmirror.app`);
|
|
441
|
+
}
|
|
427
442
|
console.log('Press Ctrl+C to stop');
|
|
443
|
+
console.log('');
|
|
428
444
|
}
|
|
429
445
|
|
|
430
446
|
async startAuthenticatedServer(authInfo) {
|
|
431
447
|
console.log('');
|
|
432
|
-
console.log('🚀 Starting authenticated Shell Mirror server...');
|
|
433
|
-
|
|
434
|
-
// Create .env file with authentication
|
|
435
|
-
await this.createEnvFile(authInfo);
|
|
436
|
-
|
|
437
|
-
// Register this server as a Mac agent
|
|
438
|
-
await this.registerAsMacAgent(authInfo);
|
|
439
|
-
|
|
440
|
-
// Find the package root directory (where server.js is located)
|
|
441
|
-
const packageRoot = path.resolve(__dirname, '..');
|
|
442
|
-
const serverPath = path.join(packageRoot, 'server.js');
|
|
448
|
+
console.log('🚀 Starting authenticated Shell Mirror with local WebSocket server...');
|
|
443
449
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
+
try {
|
|
451
|
+
// Set up local WebSocket server configuration
|
|
452
|
+
const wsPort = await NetworkUtils.findAvailablePort(8080, 10);
|
|
453
|
+
const wsConfig = NetworkUtils.generateWebSocketConfig(wsPort);
|
|
454
|
+
|
|
455
|
+
// Display network information
|
|
456
|
+
NetworkUtils.displayNetworkInfo(wsConfig);
|
|
457
|
+
|
|
458
|
+
// Create .env file with authentication and WebSocket config
|
|
459
|
+
await this.createEnvFile(authInfo, wsConfig);
|
|
460
|
+
|
|
461
|
+
// Register this server as a Mac agent with WebSocket endpoint
|
|
462
|
+
await this.registerAsMacAgent(authInfo, wsConfig);
|
|
463
|
+
|
|
464
|
+
// Start the local WebSocket server process
|
|
465
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
466
|
+
const serverPath = path.join(packageRoot, 'server.js');
|
|
467
|
+
|
|
468
|
+
console.log('🔄 Starting local WebSocket server...');
|
|
469
|
+
const serverProcess = spawn('node', [serverPath], {
|
|
470
|
+
stdio: 'inherit',
|
|
471
|
+
cwd: path.dirname(this.envFile),
|
|
472
|
+
env: {
|
|
473
|
+
...process.env,
|
|
474
|
+
WS_PORT: wsConfig.port.toString(),
|
|
475
|
+
WS_HOST: wsConfig.host,
|
|
476
|
+
LOCAL_IP: wsConfig.localIP
|
|
477
|
+
}
|
|
478
|
+
});
|
|
450
479
|
|
|
451
|
-
|
|
452
|
-
|
|
480
|
+
// Display status information
|
|
481
|
+
this.displayStatus(authInfo, wsConfig);
|
|
453
482
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
483
|
+
// Handle Ctrl+C gracefully
|
|
484
|
+
process.on('SIGINT', () => {
|
|
485
|
+
console.log('');
|
|
486
|
+
console.log('🛑 Stopping Shell Mirror and WebSocket server...');
|
|
487
|
+
serverProcess.kill('SIGINT');
|
|
488
|
+
process.exit(0);
|
|
489
|
+
});
|
|
461
490
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
491
|
+
// Wait for server process
|
|
492
|
+
serverProcess.on('close', (code) => {
|
|
493
|
+
if (code !== 0) {
|
|
494
|
+
console.error(`❌ Server exited with code ${code}`);
|
|
495
|
+
process.exit(code);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
} catch (error) {
|
|
500
|
+
console.error('❌ Failed to start WebSocket server:', error.message);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
469
503
|
}
|
|
470
504
|
|
|
471
|
-
async registerAsMacAgent(authInfo) {
|
|
472
|
-
console.log('🔗 Registering as Mac agent...');
|
|
505
|
+
async registerAsMacAgent(authInfo, wsConfig) {
|
|
506
|
+
console.log('🔗 Registering as Mac agent with WebSocket endpoint...');
|
|
473
507
|
console.log(` User: ${authInfo.email}`);
|
|
474
508
|
console.log(` Machine: ${os.hostname()}`);
|
|
509
|
+
console.log(` WebSocket: ${wsConfig.wsUrl}`);
|
|
475
510
|
|
|
476
511
|
try {
|
|
477
512
|
const agentId = `local-${os.hostname()}-${Date.now()}`;
|
|
@@ -481,9 +516,11 @@ ACCESS_TOKEN=${authInfo.accessToken}
|
|
|
481
516
|
ownerName: authInfo.name,
|
|
482
517
|
ownerToken: authInfo.accessToken,
|
|
483
518
|
machineName: os.hostname(),
|
|
484
|
-
agentVersion: '1.
|
|
519
|
+
agentVersion: '1.5.17',
|
|
485
520
|
capabilities: ['terminal', 'websocket'],
|
|
486
|
-
serverPort:
|
|
521
|
+
serverPort: wsConfig.port,
|
|
522
|
+
websocketUrl: wsConfig.wsUrl,
|
|
523
|
+
localIP: wsConfig.localIP
|
|
487
524
|
};
|
|
488
525
|
|
|
489
526
|
console.log(` Agent ID: ${agentId}`);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Network utilities for local WebSocket server setup
|
|
5
|
+
*/
|
|
6
|
+
class NetworkUtils {
|
|
7
|
+
/**
|
|
8
|
+
* Get the local IP address for WebSocket server binding
|
|
9
|
+
* Prioritizes Wi-Fi and Ethernet interfaces over others
|
|
10
|
+
*/
|
|
11
|
+
static getLocalIP() {
|
|
12
|
+
const interfaces = os.networkInterfaces();
|
|
13
|
+
|
|
14
|
+
// Priority order for interface selection
|
|
15
|
+
const priorityOrder = [
|
|
16
|
+
'Wi-Fi', // macOS Wi-Fi
|
|
17
|
+
'en0', // macOS primary interface
|
|
18
|
+
'en1', // macOS secondary interface
|
|
19
|
+
'eth0', // Linux Ethernet
|
|
20
|
+
'wlan0', // Linux Wi-Fi
|
|
21
|
+
'Ethernet', // Windows Ethernet
|
|
22
|
+
'Wi-Fi' // Windows Wi-Fi (might be different name)
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// First, try priority interfaces
|
|
26
|
+
for (const interfaceName of priorityOrder) {
|
|
27
|
+
const addresses = interfaces[interfaceName];
|
|
28
|
+
if (addresses) {
|
|
29
|
+
for (const addr of addresses) {
|
|
30
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
31
|
+
console.log(`🌐 Selected network interface: ${interfaceName} (${addr.address})`);
|
|
32
|
+
return addr.address;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fallback: find any non-internal IPv4 address
|
|
39
|
+
for (const interfaceName in interfaces) {
|
|
40
|
+
const addresses = interfaces[interfaceName];
|
|
41
|
+
for (const addr of addresses) {
|
|
42
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
43
|
+
console.log(`🌐 Fallback network interface: ${interfaceName} (${addr.address})`);
|
|
44
|
+
return addr.address;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Last resort: localhost (won't work for remote clients)
|
|
50
|
+
console.warn('⚠️ No external network interface found, using localhost');
|
|
51
|
+
return '127.0.0.1';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find an available port for the WebSocket server
|
|
56
|
+
* @param {number} startPort - Starting port to check
|
|
57
|
+
* @param {number} maxAttempts - Maximum number of ports to try
|
|
58
|
+
*/
|
|
59
|
+
static async findAvailablePort(startPort = 8080, maxAttempts = 10) {
|
|
60
|
+
const net = require('net');
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
63
|
+
const port = startPort + i;
|
|
64
|
+
const isAvailable = await this.checkPortAvailable(port);
|
|
65
|
+
if (isAvailable) {
|
|
66
|
+
console.log(`🔌 Found available port: ${port}`);
|
|
67
|
+
return port;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw new Error(`No available ports found in range ${startPort}-${startPort + maxAttempts - 1}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a specific port is available
|
|
76
|
+
* @param {number} port - Port to check
|
|
77
|
+
*/
|
|
78
|
+
static checkPortAvailable(port) {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
const net = require('net');
|
|
81
|
+
const server = net.createServer();
|
|
82
|
+
|
|
83
|
+
server.listen(port, (err) => {
|
|
84
|
+
if (err) {
|
|
85
|
+
resolve(false);
|
|
86
|
+
} else {
|
|
87
|
+
server.once('close', () => resolve(true));
|
|
88
|
+
server.close();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
server.on('error', () => resolve(false));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate WebSocket server configuration
|
|
98
|
+
* @param {number} port - Port for WebSocket server
|
|
99
|
+
*/
|
|
100
|
+
static generateWebSocketConfig(port) {
|
|
101
|
+
const localIP = this.getLocalIP();
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
host: '0.0.0.0', // Bind to all interfaces
|
|
105
|
+
port: port,
|
|
106
|
+
localIP: localIP,
|
|
107
|
+
wsUrl: `ws://${localIP}:${port}`,
|
|
108
|
+
httpsWsUrl: `wss://${localIP}:${port}` // For future HTTPS support
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Display network connectivity information
|
|
114
|
+
* @param {object} config - WebSocket configuration
|
|
115
|
+
*/
|
|
116
|
+
static displayNetworkInfo(config) {
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log('🌐 Network Configuration:');
|
|
119
|
+
console.log(` Local IP: ${config.localIP}`);
|
|
120
|
+
console.log(` WebSocket URL: ${config.wsUrl}`);
|
|
121
|
+
console.log(` Binding: ${config.host}:${config.port}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log('📱 Access from other devices:');
|
|
124
|
+
console.log(` Use this URL in browser: https://shellmirror.app`);
|
|
125
|
+
console.log(` Mac agent WebSocket: ${config.wsUrl}`);
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate WebSocket URL format
|
|
131
|
+
* @param {string} wsUrl - WebSocket URL to validate
|
|
132
|
+
*/
|
|
133
|
+
static validateWebSocketURL(wsUrl) {
|
|
134
|
+
try {
|
|
135
|
+
const url = new URL(wsUrl);
|
|
136
|
+
return (url.protocol === 'ws:' || url.protocol === 'wss:') &&
|
|
137
|
+
url.hostname &&
|
|
138
|
+
url.port;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = NetworkUtils;
|
package/package.json
CHANGED
package/public/app/terminal.html
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
<!-- Version Footer -->
|
|
55
55
|
<footer style="background: #ff6b35; color: white; text-align: center; padding: 10px 0; font-size: 0.8rem; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000;">
|
|
56
56
|
<div style="max-width: 1200px; margin: 0 auto;">
|
|
57
|
-
<p id="terminal-version-info">Terminal Mirror
|
|
57
|
+
<p id="terminal-version-info">Terminal Mirror • Loading version...</p>
|
|
58
58
|
</div>
|
|
59
59
|
</footer>
|
|
60
60
|
|
package/public/app/terminal.js
CHANGED
|
@@ -54,6 +54,7 @@ let dataChannel;
|
|
|
54
54
|
let user;
|
|
55
55
|
let AGENT_ID;
|
|
56
56
|
let CLIENT_ID;
|
|
57
|
+
let SELECTED_AGENT; // Store full agent data including WebSocket URL
|
|
57
58
|
|
|
58
59
|
// Auto-discover agents on page load
|
|
59
60
|
window.addEventListener('load', () => {
|
|
@@ -67,13 +68,21 @@ async function loadVersionInfo() {
|
|
|
67
68
|
const response = await fetch('/build-info.json');
|
|
68
69
|
const buildInfo = await response.json();
|
|
69
70
|
const versionElement = document.getElementById('terminal-version-info');
|
|
71
|
+
const footerElement = versionElement?.parentElement?.parentElement; // Get the footer element
|
|
72
|
+
|
|
70
73
|
if (versionElement && buildInfo) {
|
|
71
74
|
const buildDate = new Date(buildInfo.buildTime).toLocaleDateString();
|
|
72
75
|
versionElement.textContent = `Terminal Mirror v${buildInfo.version} • Built ${buildDate}`;
|
|
76
|
+
|
|
77
|
+
// Apply random footer color from build info
|
|
78
|
+
if (footerElement && buildInfo.footerColor) {
|
|
79
|
+
footerElement.style.background = buildInfo.footerColor;
|
|
80
|
+
console.log(`🎨 Applied footer color: ${buildInfo.footerColor}`);
|
|
81
|
+
}
|
|
73
82
|
}
|
|
74
83
|
} catch (error) {
|
|
75
84
|
console.log('Could not load build info for terminal:', error);
|
|
76
|
-
// Keep default version if build-info.json not available
|
|
85
|
+
// Keep default version and color if build-info.json not available
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
|
|
@@ -92,8 +101,17 @@ connectBtn.onclick = () => {
|
|
|
92
101
|
startConnection();
|
|
93
102
|
};
|
|
94
103
|
|
|
95
|
-
function connectToAgent(
|
|
96
|
-
|
|
104
|
+
function connectToAgent(agent) {
|
|
105
|
+
if (typeof agent === 'string') {
|
|
106
|
+
// Fallback for manual connection - agent is just the ID
|
|
107
|
+
AGENT_ID = agent;
|
|
108
|
+
SELECTED_AGENT = { id: agent, websocketUrl: null };
|
|
109
|
+
} else {
|
|
110
|
+
// Full agent object from discovery
|
|
111
|
+
AGENT_ID = agent.id;
|
|
112
|
+
SELECTED_AGENT = agent;
|
|
113
|
+
}
|
|
114
|
+
console.log('[CLIENT] 🔗 Connecting to agent:', SELECTED_AGENT);
|
|
97
115
|
startConnection();
|
|
98
116
|
}
|
|
99
117
|
|
|
@@ -185,8 +203,8 @@ function displayAvailableAgents(agents) {
|
|
|
185
203
|
agentDiv.style.background = '#333';
|
|
186
204
|
};
|
|
187
205
|
agentDiv.onclick = () => {
|
|
188
|
-
console.log(`[DISCOVERY] 🖱️ User clicked on agent: ${agent.id}
|
|
189
|
-
connectToAgent(agent
|
|
206
|
+
console.log(`[DISCOVERY] 🖱️ User clicked on agent: ${agent.id}`, agent);
|
|
207
|
+
connectToAgent(agent);
|
|
190
208
|
};
|
|
191
209
|
|
|
192
210
|
agentList.appendChild(agentDiv);
|
|
@@ -208,8 +226,19 @@ function displayAvailableAgents(agents) {
|
|
|
208
226
|
|
|
209
227
|
async function initialize() {
|
|
210
228
|
console.log('[CLIENT] 🚀 Initializing WebRTC connection to agent:', AGENT_ID);
|
|
211
|
-
|
|
212
|
-
|
|
229
|
+
console.log('[CLIENT] 📋 Selected agent data:', SELECTED_AGENT);
|
|
230
|
+
|
|
231
|
+
// Determine WebSocket URL - use agent's local WebSocket server if available
|
|
232
|
+
let signalingUrl;
|
|
233
|
+
if (SELECTED_AGENT && SELECTED_AGENT.websocketUrl) {
|
|
234
|
+
signalingUrl = SELECTED_AGENT.websocketUrl;
|
|
235
|
+
console.log('[CLIENT] 🌐 Using agent\'s local WebSocket server:', signalingUrl);
|
|
236
|
+
} else {
|
|
237
|
+
// Fallback to web app domain (old behavior)
|
|
238
|
+
signalingUrl = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host;
|
|
239
|
+
console.log('[CLIENT] 🌐 Using fallback WebSocket server:', signalingUrl);
|
|
240
|
+
}
|
|
241
|
+
|
|
213
242
|
ws = new WebSocket(`${signalingUrl}?role=client`);
|
|
214
243
|
|
|
215
244
|
ws.onopen = () => {
|
package/public/index.html
CHANGED
|
@@ -655,7 +655,7 @@
|
|
|
655
655
|
<!-- Version Footer -->
|
|
656
656
|
<footer style="background: #ff6b35; color: white; text-align: center; padding: 20px 0; font-size: 0.8rem;">
|
|
657
657
|
<div class="container">
|
|
658
|
-
<p id="version-info">Shell Mirror
|
|
658
|
+
<p id="version-info">Shell Mirror • Loading version...</p>
|
|
659
659
|
</div>
|
|
660
660
|
</footer>
|
|
661
661
|
</main>
|
|
@@ -695,9 +695,17 @@
|
|
|
695
695
|
const response = await fetch('/build-info.json');
|
|
696
696
|
const buildInfo = await response.json();
|
|
697
697
|
const versionElement = document.getElementById('version-info');
|
|
698
|
+
const footerElement = versionElement?.parentElement?.parentElement; // Get the footer element
|
|
699
|
+
|
|
698
700
|
if (versionElement && buildInfo) {
|
|
699
701
|
const buildDate = new Date(buildInfo.buildTime).toLocaleDateString();
|
|
700
702
|
versionElement.textContent = `Shell Mirror v${buildInfo.version} • Built ${buildDate}`;
|
|
703
|
+
|
|
704
|
+
// Apply random footer color from build info
|
|
705
|
+
if (footerElement && buildInfo.footerColor) {
|
|
706
|
+
footerElement.style.background = buildInfo.footerColor;
|
|
707
|
+
console.log(`🎨 Applied footer color: ${buildInfo.footerColor}`);
|
|
708
|
+
}
|
|
701
709
|
}
|
|
702
710
|
} catch (error) {
|
|
703
711
|
console.log('Could not load build info:', error);
|
package/server.js
CHANGED
|
@@ -255,10 +255,15 @@ function handleSignalingMessage(ws, rawMessage, senderRole) {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
// --- Server Startup ---
|
|
258
|
-
const PORT = process.env.PORT ||
|
|
259
|
-
const HOST = process.env.HOST || '0.0.0.0';
|
|
258
|
+
const PORT = process.env.WS_PORT || process.env.PORT || 8080;
|
|
259
|
+
const HOST = process.env.WS_HOST || process.env.HOST || '0.0.0.0';
|
|
260
|
+
const LOCAL_IP = process.env.WS_LOCAL_IP || process.env.LOCAL_IP || 'localhost';
|
|
261
|
+
|
|
260
262
|
server.listen(PORT, HOST, () => {
|
|
261
|
-
|
|
263
|
+
const baseUrl = process.env.BASE_URL || `http://${LOCAL_IP}:${PORT}`;
|
|
264
|
+
logToFile(`✅ Local WebSocket server is running on ${baseUrl}`);
|
|
265
|
+
logToFile(`🌐 WebSocket URL: ws://${LOCAL_IP}:${PORT}`);
|
|
266
|
+
logToFile(`🔧 Binding to: ${HOST}:${PORT}`);
|
|
262
267
|
});
|
|
263
268
|
|
|
264
269
|
// Graceful shutdown
|