From 4b49d13366d391ca72d70c31728e4db85dd76538 Mon Sep 17 00:00:00 2001 From: arc-alex Date: Thu, 28 Nov 2024 17:33:20 +0100 Subject: [PATCH 1/3] detect `EADDRINUSE` on server start --- apps/server/src/app.ts | 67 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index d1d47da974..b4aba744a8 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -3,7 +3,7 @@ import { LogOrigin, Playback, SimpleDirection, SimplePlayback } from 'ontime-typ import 'dotenv/config'; import express from 'express'; import expressStaticGzip from 'express-static-gzip'; -import http, { type Server } from 'http'; +import http, { Server } from 'http'; import cors from 'cors'; import serverTiming from 'server-timing'; @@ -158,9 +158,15 @@ export const startServer = async ( escalateErrorFn?: (error: string) => void, ): Promise<{ message: string; serverPort: number }> => { checkStart(OntimeStartOrder.InitServer); - const { serverPort } = getDataProvider().getSettings(); + const settings = getDataProvider().getSettings(); + const { serverPort } = settings; expressServer = http.createServer(app); + + // the express server must be started before the socket otherwise the on error eventlissner will not attach properly + const resultPort = await serverTryDesiredPort(expressServer, serverPort); + await getDataProvider().setSettings({ ...settings, serverPort: resultPort }); + socket.init(expressServer); /** @@ -205,19 +211,20 @@ export const startServer = async ( // TODO: pass event store to rundownservice runtimeService.init(maybeRestorePoint); - expressServer.listen(serverPort, '0.0.0.0', () => { - const nif = getNetworkInterfaces(); - consoleSuccess(`Local: http://localhost:${serverPort}/editor`); - for (const key in nif) { - const address = nif[key].address; - consoleSuccess(`Network: http://${address}:${serverPort}/editor`); - } - }); + const nif = getNetworkInterfaces(); + consoleSuccess(`Local: http://localhost:${resultPort}/editor`); + for (const key in nif) { + const address = nif[key].address; + consoleSuccess(`Network: http://${address}:${resultPort}/editor`); + } - const returnMessage = `Ontime is listening on port ${serverPort}`; + const returnMessage = `Ontime is listening on port ${resultPort}`; logger.info(LogOrigin.Server, returnMessage); - return { message: returnMessage, serverPort }; + return { + message: returnMessage, + serverPort: resultPort, + }; }; /** @@ -282,7 +289,7 @@ process.on('unhandledRejection', async (error) => { consoleError(error.stack); } generateCrashReport(error); - logger.crash(LogOrigin.Server, `Uncaught exception | ${error}`); + logger.crash(LogOrigin.Server, `Uncaught rejection | ${error}`); await shutdown(1); }); @@ -299,3 +306,37 @@ process.on('uncaughtException', async (error) => { process.once('SIGHUP', async () => shutdown(0)); process.once('SIGINT', async () => shutdown(0)); process.once('SIGTERM', async () => shutdown(0)); + +/** + * @description tries to open the server with the desired port, and if getting a `EADDRINUSE` will change to an efemeral port + * @param {http.Server}server http server object + * @param {number}desiredPort the desired port + * @returns {number} the resulting port number + * @throws any other server errors will result in a throw + */ +async function serverTryDesiredPort(server: http.Server, desiredPort: number): Promise { + return new Promise((res) => { + expressServer.once('error', (e) => { + if (testForPortInUser(e)) { + logger.error(LogOrigin.Server, `Failed open the desired port: ${desiredPort} | to moving to Ephemeral port`); + server.listen(0, '0.0.0.0', () => { + const port: number = server.address().port; + res(port); + }); + } else { + throw e; + } + }); + server.listen(desiredPort, '0.0.0.0', () => { + const port: number = server.address().port; + res(port); + }); + }); +} + +function testForPortInUser(err: unknown) { + if (typeof err === 'object' && 'code' in err && err.code === 'EADDRINUSE') { + return true; + } + return false; +} From c9013fc8cf9718d445b8ae809d24fdcaf9eb9141 Mon Sep 17 00:00:00 2001 From: arc-alex Date: Thu, 28 Nov 2024 17:38:20 +0100 Subject: [PATCH 2/3] @ts-expect-error --- apps/server/src/app.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index b4aba744a8..2ee18c32c4 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -320,6 +320,7 @@ async function serverTryDesiredPort(server: http.Server, desiredPort: number): P if (testForPortInUser(e)) { logger.error(LogOrigin.Server, `Failed open the desired port: ${desiredPort} | to moving to Ephemeral port`); server.listen(0, '0.0.0.0', () => { + // @ts-expect-error TODO: find proper documentation for this api const port: number = server.address().port; res(port); }); @@ -328,6 +329,7 @@ async function serverTryDesiredPort(server: http.Server, desiredPort: number): P } }); server.listen(desiredPort, '0.0.0.0', () => { + // @ts-expect-error TODO: find proper documentation for this api const port: number = server.address().port; res(port); }); From c9545d06e2db2a5953c25934767284f22f011c37 Mon Sep 17 00:00:00 2001 From: arc-alex Date: Fri, 29 Nov 2024 21:10:52 +0100 Subject: [PATCH 3/3] send portError to electron --- apps/server/src/app.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 2ee18c32c4..f44360da7c 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -156,15 +156,16 @@ export const initAssets = async () => { */ export const startServer = async ( escalateErrorFn?: (error: string) => void, -): Promise<{ message: string; serverPort: number }> => { +): Promise<{ message: string; serverPort: number; portError: boolean }> => { checkStart(OntimeStartOrder.InitServer); const settings = getDataProvider().getSettings(); - const { serverPort } = settings; + const { serverPort: desiredPort } = settings; expressServer = http.createServer(app); // the express server must be started before the socket otherwise the on error eventlissner will not attach properly - const resultPort = await serverTryDesiredPort(expressServer, serverPort); + const resultPort = await serverTryDesiredPort(expressServer, desiredPort); + const portError = resultPort !== desiredPort; await getDataProvider().setSettings({ ...settings, serverPort: resultPort }); socket.init(expressServer); @@ -224,6 +225,7 @@ export const startServer = async ( return { message: returnMessage, serverPort: resultPort, + portError, }; };