From 12c2304dde4b2e17c5eb8dbaef0ee871ac4389c0 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 7 Jul 2025 19:46:01 -0400 Subject: [PATCH 01/10] fix(webui): Proxy socket.io requests when localhost resolves to multiple addresses. --- components/webui/server/package-lock.json | 74 ++++++++++++++++++- components/webui/server/package.json | 1 + .../src/plugins/MongoSocketIoServer/index.ts | 33 +++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/components/webui/server/package-lock.json b/components/webui/server/package-lock.json index 9461a27621..976f56548c 100644 --- a/components/webui/server/package-lock.json +++ b/components/webui/server/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/s3-request-presigner": "^3.758.0", "@fastify/autoload": "^6.3.0", "@fastify/env": "^5.0.2", + "@fastify/http-proxy": "^11.3.0", "@fastify/mongodb": "^9.0.2", "@fastify/mysql": "^5.0.2", "@fastify/rate-limit": "^10.2.2", @@ -1354,6 +1355,28 @@ "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", "license": "MIT" }, + "node_modules/@fastify/http-proxy": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@fastify/http-proxy/-/http-proxy-11.3.0.tgz", + "integrity": "sha512-FXFxkdTlXqVI11fqlxmHqOPzIo0elBA60o3bfdh2seD44KWOBBzelzCVgs1OelrxuADCyWUQp2ZxA2wp3mqQMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/reply-from": "^12.0.2", + "fast-querystring": "^1.1.2", + "fastify-plugin": "^5.0.1", + "ws": "^8.18.0" + } + }, "node_modules/@fastify/merge-json-schemas": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", @@ -1444,6 +1467,31 @@ "toad-cache": "^3.7.0" } }, + "node_modules/@fastify/reply-from": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@fastify/reply-from/-/reply-from-12.1.0.tgz", + "integrity": "sha512-5SvMj0NnAAhno/MIv+bErCfzYxcqTueqUxCyub/i+68/CqYWroYg0/p8D0ZhrSQcDigowaKk+MEQSoLjbGwZ+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "end-of-stream": "^1.4.4", + "fast-content-type-parse": "^2.0.0", + "fast-querystring": "^1.1.2", + "fastify-plugin": "^5.0.1", + "toad-cache": "^3.7.0", + "undici": "^7.0.0" + } + }, "node_modules/@fastify/send": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@fastify/send/-/send-3.3.1.tgz", @@ -6590,6 +6638,22 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-copy": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", @@ -12311,6 +12375,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -12747,7 +12820,6 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/components/webui/server/package.json b/components/webui/server/package.json index a4ac87fac9..0d71297067 100644 --- a/components/webui/server/package.json +++ b/components/webui/server/package.json @@ -25,6 +25,7 @@ "@fastify/mongodb": "^9.0.2", "@fastify/mysql": "^5.0.2", "@fastify/rate-limit": "^10.2.2", + "@fastify/http-proxy": "^11.3.0", "@fastify/sensible": "^6.0.3", "@fastify/static": "^8.1.1", "@fastify/type-provider-typebox": "^5.1.0", diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 9fa4c8e0af..7308456921 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -3,6 +3,10 @@ // TODO: Move listeners to a separate file to reduce lines // Reference: https://github.com/socketio/socket.io/blob/main/examples/basic-crud-application/server/lib/todo-management/todo.handlers.ts + +import {lookup as dnsLookup} from "node:dns"; + +import fastifyHttpProxy from "@fastify/http-proxy"; import { FastifyInstance, FastifyPluginAsync, @@ -77,6 +81,35 @@ class MongoSocketIoServer { InterServerEvents, SocketData >(fastify.server); + + // Fastify listens on all resolved addresses for localhost (e.g. `127.0.0.1`, `::1`), but + // socket.io only uses the main one. When multiple addresses are resolved, we proxy + // socket.io requests from secondary addresses to the main server. + dnsLookup(fastify.config.HOST, {all: true}, (err, addresses) => { + if (null !== err) { + fastify.log.error(`DNS lookup failed: ${err.message}`); + + return; + } + const [firstAddress] = addresses; + if ("undefined" === typeof firstAddress) { + fastify.log.error("No addresses found for the provided host."); + + return; + } + if (1 < addresses.length) { + fastify.log.warn( + `Multiple addresses found for ${fastify.config.HOST}: ${ + JSON.stringify(addresses)}. ` + + "Proxying all socket.io requests to the main server." + ); + fastify.register(fastifyHttpProxy, { + upstream: `http://${fastify.config.HOST}:${fastify.config.PORT}/socket.io/`, + prefix: "/socket.io/", + }); + } + }); + this.#registerEventListeners(); } From 5e408730490fe5848ddbbd4554aa0c5b233312d9 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 7 Jul 2025 20:34:16 -0400 Subject: [PATCH 02/10] add lines between blocks --- components/webui/server/src/plugins/MongoSocketIoServer/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 7308456921..7ec4b0ad62 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -97,6 +97,7 @@ class MongoSocketIoServer { return; } + if (1 < addresses.length) { fastify.log.warn( `Multiple addresses found for ${fastify.config.HOST}: ${ From 4067548113f9cf145b83e992135d747927161e44 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 7 Jul 2025 20:34:31 -0400 Subject: [PATCH 03/10] add lines between blocks --- components/webui/server/src/plugins/MongoSocketIoServer/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 7ec4b0ad62..ad1f31e771 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -91,6 +91,7 @@ class MongoSocketIoServer { return; } + const [firstAddress] = addresses; if ("undefined" === typeof firstAddress) { fastify.log.error("No addresses found for the provided host."); From f252bafa4d0998ce12f0e52ede45c67fe4ae30ba Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 8 Jul 2025 12:39:27 -0400 Subject: [PATCH 04/10] Remove redundant check for undefined first address in address resolution --- .../webui/server/src/plugins/MongoSocketIoServer/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index ad1f31e771..87ffa3ee8f 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -92,13 +92,6 @@ class MongoSocketIoServer { return; } - const [firstAddress] = addresses; - if ("undefined" === typeof firstAddress) { - fastify.log.error("No addresses found for the provided host."); - - return; - } - if (1 < addresses.length) { fastify.log.warn( `Multiple addresses found for ${fastify.config.HOST}: ${ From b28de193278c970eee58d9ae08c35b0e750dd36b Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 8 Jul 2025 13:01:54 -0400 Subject: [PATCH 05/10] Update DNS lookup to use promises to avoid timing issues and improve error handling --- .../src/plugins/MongoSocketIoServer/index.ts | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 87ffa3ee8f..8e544617c5 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -4,7 +4,7 @@ // Reference: https://github.com/socketio/socket.io/blob/main/examples/basic-crud-application/server/lib/todo-management/todo.handlers.ts -import {lookup as dnsLookup} from "node:dns"; +import {lookup as dnsLookup} from "node:dns/promises"; import fastifyHttpProxy from "@fastify/http-proxy"; import { @@ -82,29 +82,6 @@ class MongoSocketIoServer { SocketData >(fastify.server); - // Fastify listens on all resolved addresses for localhost (e.g. `127.0.0.1`, `::1`), but - // socket.io only uses the main one. When multiple addresses are resolved, we proxy - // socket.io requests from secondary addresses to the main server. - dnsLookup(fastify.config.HOST, {all: true}, (err, addresses) => { - if (null !== err) { - fastify.log.error(`DNS lookup failed: ${err.message}`); - - return; - } - - if (1 < addresses.length) { - fastify.log.warn( - `Multiple addresses found for ${fastify.config.HOST}: ${ - JSON.stringify(addresses)}. ` + - "Proxying all socket.io requests to the main server." - ); - fastify.register(fastifyHttpProxy, { - upstream: `http://${fastify.config.HOST}:${fastify.config.PORT}/socket.io/`, - prefix: "/socket.io/", - }); - } - }); - this.#registerEventListeners(); } @@ -120,6 +97,29 @@ class MongoSocketIoServer { options: DbOptions ): Promise { const mongoDb = await initializeMongoClient(options); + + // Fastify listens on all resolved addresses for localhost (e.g. `127.0.0.1`, `::1`), but + // socket.io only uses the main one. When multiple addresses are resolved, we proxy + // socket.io requests from secondary addresses to the main server. + try { + const addresses = await dnsLookup(fastify.config.HOST, {all: true}); + if (1 < addresses.length) { + fastify.log.warn( + `Multiple addresses found for ${fastify.config.HOST}: ${ + JSON.stringify(addresses)}. ` + + "Proxying all socket.io requests to the main server." + ); + fastify.register(fastifyHttpProxy, { + upstream: `http://${fastify.config.HOST}:${fastify.config.PORT}/socket.io/`, + prefix: "/socket.io/", + }); + } + } catch (e) { + fastify.log.error(`DNS lookup failed: ${e instanceof Error ? + e.message : + JSON.stringify(e)}`); + } + return new MongoSocketIoServer(fastify, mongoDb); } From 96824c83ec664639d0298f608d02b065239fa94f Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 8 Jul 2025 13:02:59 -0400 Subject: [PATCH 06/10] revert empty line --- components/webui/server/src/plugins/MongoSocketIoServer/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 8e544617c5..b6d675a299 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -81,7 +81,6 @@ class MongoSocketIoServer { InterServerEvents, SocketData >(fastify.server); - this.#registerEventListeners(); } From 16d9769e1701e3be0fe00663d795587f6af72152 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 8 Jul 2025 13:09:11 -0400 Subject: [PATCH 07/10] Update socket.io proxy to use dynamic protocol based on HTTPS configuration --- .../webui/server/src/plugins/MongoSocketIoServer/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index b6d675a299..e7aea6c1f6 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -108,8 +108,13 @@ class MongoSocketIoServer { JSON.stringify(addresses)}. ` + "Proxying all socket.io requests to the main server." ); + + const protocol = fastify.initialConfig.https ? + "https" : + "http"; + fastify.register(fastifyHttpProxy, { - upstream: `http://${fastify.config.HOST}:${fastify.config.PORT}/socket.io/`, + upstream: `${protocol}://${fastify.config.HOST}:${fastify.config.PORT}/socket.io/`, prefix: "/socket.io/", }); } From 4bb266bc845f5a5e251f871698184564ab1570d5 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Tue, 8 Jul 2025 13:09:59 -0400 Subject: [PATCH 08/10] remove redundant empty lines --- components/webui/server/src/plugins/MongoSocketIoServer/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index e7aea6c1f6..703eea18ce 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -3,7 +3,6 @@ // TODO: Move listeners to a separate file to reduce lines // Reference: https://github.com/socketio/socket.io/blob/main/examples/basic-crud-application/server/lib/todo-management/todo.handlers.ts - import {lookup as dnsLookup} from "node:dns/promises"; import fastifyHttpProxy from "@fastify/http-proxy"; From 2bd7696005b5a0c445631a5b76f6433613cf2725 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Wed, 9 Jul 2025 15:27:13 -0400 Subject: [PATCH 09/10] improve docs - Apply suggestions from code review --- .../webui/server/src/plugins/MongoSocketIoServer/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 703eea18ce..547f45eb3d 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -96,8 +96,8 @@ class MongoSocketIoServer { ): Promise { const mongoDb = await initializeMongoClient(options); - // Fastify listens on all resolved addresses for localhost (e.g. `127.0.0.1`, `::1`), but - // socket.io only uses the main one. When multiple addresses are resolved, we proxy + // Fastify listens on all resolved addresses for localhost (e.g. `::1` and `127.0.0.1`), but + // socket.io can only intercept requests on the main server which listens only on the primary address. When multiple addresses are resolved, we proxy // socket.io requests from secondary addresses to the main server. try { const addresses = await dnsLookup(fastify.config.HOST, {all: true}); From b18ace44d13ec8328bf5762274cc7f1ff17ff279 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Wed, 9 Jul 2025 15:28:31 -0400 Subject: [PATCH 10/10] reflow --- .../webui/server/src/plugins/MongoSocketIoServer/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts index 547f45eb3d..af14c0b760 100644 --- a/components/webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -97,8 +97,9 @@ class MongoSocketIoServer { const mongoDb = await initializeMongoClient(options); // Fastify listens on all resolved addresses for localhost (e.g. `::1` and `127.0.0.1`), but - // socket.io can only intercept requests on the main server which listens only on the primary address. When multiple addresses are resolved, we proxy - // socket.io requests from secondary addresses to the main server. + // socket.io can only intercept requests on the main server which listens only on the + // primary address. When multiple addresses are resolved, we proxy socket.io requests + // from secondary addresses to the main server. try { const addresses = await dnsLookup(fastify.config.HOST, {all: true}); if (1 < addresses.length) {