Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 79 additions & 68 deletions main/kds-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,85 +580,96 @@ export function startKdsServer(): Promise<void> {
res.status(500).json({ error: 'Internal server error' });
});

let currentKdsPort = KDS_PORT;
const baseKdsPort = parseInt(process.env.KDS_PORT || '3002', 10);
let currentKdsPort = baseKdsPort;
let attempts = 0;

let listeningServer: http.Server;
const onListening = () => {
if (stopping) {
try { listeningServer.close(); } catch { return; }
return;
}
startReject = null;
const address = listeningServer.address();
activeKdsPort = address && typeof address !== 'string' ? address.port : currentKdsPort;
console.log(`[KDS Server] HTTP server running on http://localhost:${activeKdsPort}`);

if (listeningServer) {
// noServer + a manual 'upgrade' handler so a disabled KDS can 404 the
// upgrade instead of completing it — see main/server.ts for the same
// pattern on the primary API server (issue #133).
const wss = new WebSocketServer({ noServer: true });
kdsWss = wss;
setupKdsWebSocket(wss);

listeningServer.on('upgrade', (request, socket, head) => {
const pathname = (request.url || '').split('?')[0];
if (pathname !== '/kds') {
socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}

if (isDatabaseMaintenanceActive()) {
socket.write('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}
const listeningServer = http.createServer(app);
kdsServer = listeningServer;
installHttpShutdownTracking(listeningServer);

if (!isKdsEnabled()) {
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
socket.destroy();
return;
}
const tryListen = () => {
const attemptedPort = currentKdsPort;
const onListening = () => {
if (stopping) {
try { listeningServer.close(); } catch { return; }
return;
}
startReject = null;
listeningServer.off('error', onError);
const address = listeningServer.address();
activeKdsPort = address && typeof address !== 'string' ? address.port : attemptedPort;
console.log(`[KDS Server] HTTP server running on http://localhost:${activeKdsPort}`);

if (listeningServer) {
// noServer + a manual 'upgrade' handler so a disabled KDS can 404 the
// upgrade instead of completing it — see main/server.ts for the same
// pattern on the primary API server (issue #133).
const wss = new WebSocketServer({ noServer: true });
kdsWss = wss;
setupKdsWebSocket(wss);

listeningServer.on('upgrade', (request, socket, head) => {
const pathname = (request.url || '').split('?')[0];
if (pathname !== '/kds') {
socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}

try {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
} catch (error) {
console.error('[KDS Server] WebSocket upgrade failed:', error);
socket.destroy();
}
});
if (isDatabaseMaintenanceActive()) {
socket.write('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}

console.log(`[KDS Server] WebSocket running on ws://localhost:${activeKdsPort}/kds`);
}
if (!isKdsEnabled()) {
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
socket.destroy();
return;
}

resolve();
};
try {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
} catch (error) {
console.error('[KDS Server] WebSocket upgrade failed:', error);
socket.destroy();
}
});

listeningServer = app.listen(currentKdsPort, '0.0.0.0', onListening);
kdsServer = listeningServer;
installHttpShutdownTracking(listeningServer);
console.log(`[KDS Server] WebSocket running on ws://localhost:${activeKdsPort}/kds`);
}

listeningServer.on('error', (err: NodeJS.ErrnoException) => {
if (stopping) return;
if (err.code === 'EADDRINUSE') {
attempts++;
if (attempts >= 10) {
const errorMsg = `[KDS Server] Failed to bind to any port after 10 attempts starting from ${KDS_PORT}`;
console.error(errorMsg);
reject(new Error(errorMsg));
resolve();
};

const onError = (err: NodeJS.ErrnoException) => {
if (stopping) return;
listeningServer.off('listening', onListening);
if (err.code === 'EADDRINUSE' || err.code === 'EACCES') {
attempts++;
if (attempts >= 10) {
const errorMsg = `[KDS Server] Failed to bind to any port after 10 attempts starting from ${baseKdsPort}`;
console.error(errorMsg);
reject(new Error(errorMsg));
return;
}
currentKdsPort++;
console.log(`[KDS Server] Port ${attemptedPort} in use (${err.code}), trying ${currentKdsPort}`);
tryListen();
return;
}
currentKdsPort++;
console.log(`[KDS Server] Port ${currentKdsPort - 1} in use, trying ${currentKdsPort}`);
listeningServer.listen(currentKdsPort, '0.0.0.0', onListening);
} else {
reject(err);
}
});
};

listeningServer.once('listening', onListening);
listeningServer.once('error', onError);
listeningServer.listen(attemptedPort, '0.0.0.0');
};

tryListen();
});
}

Expand Down
9 changes: 5 additions & 4 deletions main/server-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ export function startServerApp(): Promise<void> {
res.status(500).json({ error: 'Internal server error' });
});

let currentPort = SERVER_APP_PORT;
const baseServerAppPort = parseInt(process.env.SERVER_APP_PORT || String(SERVER_APP_PORT), 10);
let currentPort = baseServerAppPort;
let attempts = 0;
const listeningServer = http.createServer(app);
serverApp = listeningServer;
Expand All @@ -317,16 +318,16 @@ export function startServerApp(): Promise<void> {
const onError = (err: NodeJS.ErrnoException) => {
if (stopping) return;
listeningServer.off('listening', onListening);
if (err.code === 'EADDRINUSE') {
if (err.code === 'EADDRINUSE' || err.code === 'EACCES') {
attempts++;
if (attempts >= 10) {
const errorMsg = `[Server App] Failed to bind to any port after 10 attempts starting from ${SERVER_APP_PORT}`;
const errorMsg = `[Server App] Failed to bind to any port after 10 attempts starting from ${baseServerAppPort}`;
console.error(errorMsg);
reject(new Error(errorMsg));
return;
}
currentPort++;
console.log(`[Server App] Port ${attemptedPort} in use, trying ${currentPort}`);
console.log(`[Server App] Port ${attemptedPort} in use (${err.code}), trying ${currentPort}`);
tryListen();
return;
}
Expand Down
168 changes: 90 additions & 78 deletions main/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,96 +273,108 @@ export function startServer(): Promise<void> {
res.status(status).json({ error: status >= 500 ? 'Internal server error' : (err.message || 'Client error') });
});

let currentPort = PORT;
const basePort = parseInt(process.env.PORT || '3001', 10);
let currentPort = basePort;
let attempts = 0;

let listeningServer: http.Server;
const onListening = () => {
if (stopping) {
try { listeningServer.close(); } catch { return; }
return;
}
startReject = null;
const address = listeningServer.address();
activePort = address && typeof address !== 'string' ? address.port : currentPort;
console.log(`[Server] HTTP server running on http://localhost:${activePort}`);

if (listeningServer) {
// noServer + a manual 'upgrade' handler (rather than passing `server`
// straight to WebSocketServer) so a disabled KDS can 404 the upgrade
// instead of completing it — checked fresh on every request since
// kds_enabled can change at runtime without a restart (issue #133).
const websocketServer = new WebSocketServer({ noServer: true });
wss = websocketServer;
setupKdsWebSocket(websocketServer);

listeningServer.on('upgrade', (request, socket, head) => {
const pathname = (request.url || '').split('?')[0];
if (pathname !== '/kds') {
socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}
const listeningServer = http.createServer(app);
server = listeningServer;
installHttpShutdownTracking(listeningServer);

if (isDatabaseMaintenanceActive()) {
socket.write('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}
const tryListen = () => {
const attemptedPort = currentPort;
const onListening = () => {
if (stopping) {
try { listeningServer.close(); } catch { return; }
return;
}
startReject = null;
listeningServer.off('error', onError);
const address = listeningServer.address();
activePort = address && typeof address !== 'string' ? address.port : attemptedPort;
console.log(`[Server] HTTP server running on http://localhost:${activePort}`);

if (listeningServer) {
// noServer + a manual 'upgrade' handler (rather than passing `server`
// straight to WebSocketServer) so a disabled KDS can 404 the upgrade
// instead of completing it — checked fresh on every request since
// kds_enabled can change at runtime without a restart (issue #133).
const websocketServer = new WebSocketServer({ noServer: true });
wss = websocketServer;
setupKdsWebSocket(websocketServer);

listeningServer.on('upgrade', (request, socket, head) => {
const pathname = (request.url || '').split('?')[0];
if (pathname !== '/kds') {
socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}

if (!isKdsEnabled()) {
// Pretend the endpoint doesn't exist rather than confirming it's
// just disabled — less to probe from a stale/misconfigured KDS
// device on the LAN (issue #133).
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
socket.destroy();
return;
}
if (isDatabaseMaintenanceActive()) {
socket.write('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
socket.destroy();
return;
}

try {
websocketServer.handleUpgrade(request, socket, head, (ws) => {
websocketServer.emit('connection', ws, request);
});
} catch (error) {
console.error('[Server] KDS WebSocket upgrade failed:', error);
socket.destroy();
}
});
if (!isKdsEnabled()) {
// Pretend the endpoint doesn't exist rather than confirming it's
// just disabled — less to probe from a stale/misconfigured KDS
// device on the LAN (issue #133).
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
socket.destroy();
return;
}

console.log(`[Server] KDS WebSocket running on ws://localhost:${activePort}/kds`);
}
try {
websocketServer.handleUpgrade(request, socket, head, (ws) => {
websocketServer.emit('connection', ws, request);
});
} catch (error) {
console.error('[Server] KDS WebSocket upgrade failed:', error);
socket.destroy();
}
});

// main/index.ts (Electron) also calls this; dev-server and pm2 boot
// through here instead and would otherwise start with module defaults.
try {
initWhatsAppFromDb();
} catch (error) {
console.error('[Server] WhatsApp startup initialization failed:', error);
}
console.log(`[Server] KDS WebSocket running on ws://localhost:${activePort}/kds`);
}

resolve();
};
listeningServer = app.listen(currentPort, '0.0.0.0', onListening);
server = listeningServer;
installHttpShutdownTracking(listeningServer);
// main/index.ts (Electron) also calls this; dev-server and pm2 boot
// through here instead and would otherwise start with module defaults.
try {
initWhatsAppFromDb();
} catch (error) {
console.error('[Server] WhatsApp startup initialization failed:', error);
}

listeningServer.on('error', (err: NodeJS.ErrnoException) => {
if (stopping) return;
if (err.code === 'EADDRINUSE') {
attempts++;
if (attempts >= 10) {
const errorMsg = `[Server] Failed to bind to any port after 10 attempts starting from ${PORT}`;
console.error(errorMsg);
reject(new Error(errorMsg));
resolve();
};

const onError = (err: NodeJS.ErrnoException) => {
if (stopping) return;
listeningServer.off('listening', onListening);
if (err.code === 'EADDRINUSE' || err.code === 'EACCES') {
attempts++;
if (attempts >= 10) {
const errorMsg = `[Server] Failed to bind to any port after 10 attempts starting from ${basePort}`;
console.error(errorMsg);
reject(new Error(errorMsg));
return;
}
currentPort++;
console.log(`[Server] Port ${attemptedPort} in use (${err.code}), trying ${currentPort}`);
tryListen();
return;
}
currentPort++;
console.log(`[Server] Port ${currentPort - 1} in use, trying ${currentPort}`);
listeningServer.listen(currentPort, '0.0.0.0');
} else {
reject(err);
}
});
};

listeningServer.once('listening', onListening);
listeningServer.once('error', onError);
listeningServer.listen(attemptedPort, '0.0.0.0');
};

tryListen();
});
}

Expand Down
Loading
Loading