From 541b9fa53c2ee2a223fc4bb10e97bb11455ec020 Mon Sep 17 00:00:00 2001 From: Neko Date: Thu, 14 May 2026 01:53:41 +0800 Subject: [PATCH] fix(server-runtime): ignore duplicate websocket listener (#1829) --- apps/stage-tamagotchi/electron.vite.config.ts | 2 ++ packages/server-runtime/src/server.test.ts | 13 +++++++++++++ packages/server-runtime/src/server/index.ts | 11 +++++++++++ 3 files changed, 26 insertions(+) diff --git a/apps/stage-tamagotchi/electron.vite.config.ts b/apps/stage-tamagotchi/electron.vite.config.ts index 53f11d302..996f1cd48 100644 --- a/apps/stage-tamagotchi/electron.vite.config.ts +++ b/apps/stage-tamagotchi/electron.vite.config.ts @@ -69,6 +69,8 @@ export default defineConfig({ resolve: { alias: { '@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')), + '@proj-airi/server-runtime/server': resolve(join(import.meta.dirname, '..', '..', 'packages', 'server-runtime', 'src', 'server', 'index.ts')), + '@proj-airi/server-runtime': resolve(join(import.meta.dirname, '..', '..', 'packages', 'server-runtime', 'src', 'index.ts')), }, }, }, diff --git a/packages/server-runtime/src/server.test.ts b/packages/server-runtime/src/server.test.ts index f5e02882e..8d912ff6a 100644 --- a/packages/server-runtime/src/server.test.ts +++ b/packages/server-runtime/src/server.test.ts @@ -90,6 +90,19 @@ describe('createServer', async () => { await retryStart }) + it('treats EADDRINUSE as an existing listener instead of failing startup', async () => { + const server = createServer({ hostname: '127.0.0.1', port: 6121 }) + + const startTask = server.start() + const error = new Error('listen EADDRINUSE: address already in use 127.0.0.1:6121') as NodeJS.ErrnoException + error.code = 'EADDRINUSE' + serveMocks.rejectServe(error) + + await expect(startTask).resolves.toBeUndefined() + expect(serveMocks.disposeCall).toHaveBeenCalledTimes(1) + expect(serveMocks.closeCall).toHaveBeenCalledWith(true) + }) + it('merges nested config updates instead of replacing sibling logger settings', async () => { const server = createServer({ hostname: '127.0.0.1', diff --git a/packages/server-runtime/src/server/index.ts b/packages/server-runtime/src/server/index.ts index f772e92dd..2c1d8994e 100644 --- a/packages/server-runtime/src/server/index.ts +++ b/packages/server-runtime/src/server/index.ts @@ -32,6 +32,13 @@ export interface Server { updateConfig: (newOptions: ServerOptions) => void } +function isAddressInUseError(error: unknown) { + return typeof error === 'object' + && error !== null + && 'code' in error + && (error as NodeJS.ErrnoException).code === 'EADDRINUSE' +} + /** * Collects local IP addresses that can be used to reach the server from the LAN. * @@ -189,6 +196,10 @@ export function createServer(opts?: ServerOptions): Server { serverInstance = null h3App.dispose() await instance.close(true).catch(() => {}) + if (isAddressInUseError(error)) { + log.withError(error).warn('WebSocket server port already in use, assuming an existing listener is available') + return + } log.withError(error).error('failed to start WebSocket server') throw error }