From 07d238d5b933e9b6cee0e38f67259315d2eb0abd Mon Sep 17 00:00:00 2001 From: oddlama Date: Thu, 23 Mar 2023 17:08:23 +0100 Subject: [PATCH] Support hosting the frontend on a Unix socket (#17123) --- lib/extension/frontend.ts | 9 +++++++-- lib/util/settings.schema.json | 5 +++-- test/frontend.test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/extension/frontend.ts b/lib/extension/frontend.ts index cc8087a1f..0a195d075 100644 --- a/lib/extension/frontend.ts +++ b/lib/extension/frontend.ts @@ -74,8 +74,13 @@ export default class Frontend extends Extension { this.wss = new WebSocket.Server({noServer: true}); this.wss.on('connection', this.onWebSocketConnection); - this.server.listen(this.port, this.host); - logger.info(`Started frontend on port ${this.host}:${this.port}`); + if (this.host.startsWith('/')) { + this.server.listen(this.host); + logger.info(`Started frontend on socket ${this.host}`); + } else { + this.server.listen(this.port, this.host); + logger.info(`Started frontend on port ${this.host}:${this.port}`); + } } override async stop(): Promise { diff --git a/lib/util/settings.schema.json b/lib/util/settings.schema.json index 1ee123062..fe74ef0db 100644 --- a/lib/util/settings.schema.json +++ b/lib/util/settings.schema.json @@ -366,14 +366,15 @@ "port": { "type": "number", "title": "Port", - "description": "Frontend binding port", + "description": "Frontend binding port. Ignored when using a unix domain socket", "default": 8080, "requiresRestart": true }, "host": { "type": "string", "title": "Bind host", - "description": "Frontend binding host", + "description": "Frontend binding host. Binds to a unix socket when an absolute path is given instead.", + "examples": ["127.0.0.1", "/run/zigbee2mqtt/zigbee2mqtt.sock"], "default": "0.0.0.0", "requiresRestart": true }, diff --git a/test/frontend.test.js b/test/frontend.test.js index c7dc24266..d94dfa263 100644 --- a/test/frontend.test.js +++ b/test/frontend.test.js @@ -128,6 +128,32 @@ describe('Frontend', () => { expect(mockWSClient.implementation.terminate).toHaveBeenCalledTimes(1); expect(mockHTTP.implementation.close).toHaveBeenCalledTimes(1); expect(mockWS.implementation.close).toHaveBeenCalledTimes(1); + mockWS.implementation.close.mockClear(); + mockHTTP.implementation.close.mockClear(); + mockHTTP.implementation.listen.mockClear(); + mockHTTPS.implementation.listen.mockClear(); + }); + + it('Start/stop unix socket', async () => { + settings.set(['frontend'], {host: "/tmp/zigbee2mqtt.sock"}); + controller = new Controller(jest.fn(), jest.fn()); + await controller.start(); + expect(mockNodeStatic.variables.path).toBe("my/dummy/path"); + expect(mockHTTP.implementation.listen).toHaveBeenCalledWith("/tmp/zigbee2mqtt.sock"); + const mockWSClient = { + implementation: { + terminate: jest.fn(), + send: jest.fn(), + }, + events: {}, + }; + mockWS.implementation.clients.push(mockWSClient.implementation); + await controller.stop(); + expect(mockWSClient.implementation.terminate).toHaveBeenCalledTimes(1); + expect(mockHTTP.implementation.close).toHaveBeenCalledTimes(1); + expect(mockWS.implementation.close).toHaveBeenCalledTimes(1); + mockWS.implementation.close.mockClear(); + mockHTTP.implementation.close.mockClear(); mockHTTP.implementation.listen.mockClear(); mockHTTPS.implementation.listen.mockClear(); });