@matterbridge/thread 3.10.13-dev-20261007-320f739 → 3.10.13-dev-20261008-55d98fb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/backend.js DELETED
@@ -1,369 +0,0 @@
1
- import EventEmitter from 'node:events';
2
- import path from 'node:path';
3
- import { NODE_STORAGE_DIR, } from '@matterbridge/types';
4
- import { getParameter, hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
5
- import { writeDiagnostic } from '@matterbridge/utils/diagnostic';
6
- import { getErrorMessage, inspectError, logError } from '@matterbridge/utils/error';
7
- import { logModuleLoaded } from '@matterbridge/utils/loader';
8
- import { fireAndForget } from '@matterbridge/utils/wait';
9
- import { AnsiLogger, rs, UNDERLINE, UNDERLINEOFF } from 'node-ansi-logger';
10
- import { NodeStorageManager } from 'node-persist-manager';
11
- import { BroadcastServer } from './broadcastServer.js';
12
- logModuleLoaded('Backend');
13
- export class Backend extends EventEmitter {
14
- debug;
15
- verbose;
16
- diagnostic;
17
- log;
18
- matterbridge;
19
- server;
20
- port = 8283;
21
- listening = false;
22
- httpServer;
23
- httpsServer;
24
- secure;
25
- requestCert;
26
- backendExpress;
27
- backendWsServer;
28
- storedPassword = undefined;
29
- authClients = new Set();
30
- authClientsTimeout = undefined;
31
- constructor(matterbridge) {
32
- super();
33
- this.debug = hasAnyParameter('debug', 'verbose', 'debug-backend', 'verbose-backend');
34
- this.verbose = hasAnyParameter('verbose', 'verbose-backend');
35
- this.diagnostic = hasAnyParameter('diagnostic', 'diagnostic-backend');
36
- this.secure = hasAnyParameter('ssl', 'tls', 'mtls');
37
- this.requestCert = hasParameter('mtls');
38
- this.matterbridge = matterbridge;
39
- this.log = new AnsiLogger({
40
- logName: 'Backend',
41
- logNameColor: '\x1b[38;5;97m',
42
- logTimestampFormat: 4,
43
- logLevel: this.debug ? "debug" : "info",
44
- });
45
- this.server = new BroadcastServer('frontend', this.log);
46
- this.server.on('broadcast_message', (msg) => fireAndForget(this.broadcastMsgHandler(msg), this.log, 'Broadcast message handler'));
47
- }
48
- destroy() {
49
- this.server.close();
50
- }
51
- async broadcastMsgHandler(msg) {
52
- if (this.server.isWorkerRequest(msg)) {
53
- switch (msg.type) {
54
- case 'get_log_level':
55
- this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
56
- break;
57
- case 'set_log_level':
58
- this.log.logLevel = msg.params.logLevel;
59
- this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
60
- break;
61
- }
62
- }
63
- }
64
- async start(port = 8283) {
65
- this.log.debug('Starting backend...');
66
- this.port = port;
67
- const start = performance.now();
68
- const nodeStorage = new NodeStorageManager({
69
- dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR),
70
- writeQueue: false,
71
- expiredInterval: undefined,
72
- logging: false,
73
- });
74
- try {
75
- const nodeContext = await nodeStorage.createStorage('matterbridge');
76
- try {
77
- this.storedPassword = await nodeContext.get('password', '');
78
- }
79
- finally {
80
- await nodeContext.close();
81
- }
82
- }
83
- finally {
84
- await nodeStorage.close();
85
- if (this.diagnostic)
86
- writeDiagnostic('Backend', `Loading storedPassword took ${(performance.now() - start).toFixed(2)} ms`);
87
- }
88
- const { BackendExpress } = await import('./backendExpress.js');
89
- const { BackendWsServer } = await import('./backendWsServer.js');
90
- this.backendExpress = new BackendExpress(this.matterbridge, this);
91
- this.backendWsServer = new BackendWsServer(this.matterbridge, this);
92
- if (this.secure) {
93
- let cert;
94
- let key;
95
- let ca;
96
- let fullChain;
97
- let pfx;
98
- let passphrase;
99
- let httpsServerOptions;
100
- const fs = await import('node:fs');
101
- if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
102
- try {
103
- pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
104
- this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
105
- }
106
- catch (error) {
107
- logError(this.log, `Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`, error);
108
- this.emit('server_error', error);
109
- return;
110
- }
111
- try {
112
- passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
113
- passphrase = passphrase.trim();
114
- this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
115
- }
116
- catch (error) {
117
- logError(this.log, `Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`, error);
118
- this.emit('server_error', error);
119
- return;
120
- }
121
- httpsServerOptions = { pfx, passphrase };
122
- }
123
- else {
124
- try {
125
- cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
126
- this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
127
- }
128
- catch (error) {
129
- logError(this.log, `Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`, error);
130
- this.emit('server_error', error);
131
- return;
132
- }
133
- try {
134
- key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem'), 'utf8');
135
- this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}`);
136
- }
137
- catch (error) {
138
- logError(this.log, `Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}`, error);
139
- this.emit('server_error', error);
140
- return;
141
- }
142
- try {
143
- ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem'), 'utf8');
144
- fullChain = `${cert}\n${ca}`;
145
- this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')}`);
146
- }
147
- catch (error) {
148
- this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')} not loaded: ${getErrorMessage(error)}`);
149
- }
150
- httpsServerOptions = { cert: fullChain ?? cert, key, ca };
151
- }
152
- if (this.requestCert) {
153
- httpsServerOptions.requestCert = true;
154
- httpsServerOptions.rejectUnauthorized = true;
155
- }
156
- const https = await import('node:https');
157
- await this.backendExpress.start();
158
- try {
159
- this.log.debug(`Creating HTTPS server...`);
160
- this.httpsServer = https.createServer(httpsServerOptions, this.backendExpress.expressApp);
161
- }
162
- catch (error) {
163
- logError(this.log, `Failed to create HTTPS server`, error);
164
- this.emit('server_error', error);
165
- await this.backendExpress.stop();
166
- return;
167
- }
168
- await this.backendWsServer.start();
169
- this.httpsServer.listen(this.port, getParameter('bind'), () => {
170
- const addr = this.httpsServer?.address();
171
- if (addr && typeof addr !== 'string') {
172
- this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
173
- }
174
- if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
175
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
176
- if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
177
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
178
- this.listening = true;
179
- this.emit('server_listening', 'https', this.port);
180
- });
181
- this.httpsServer.on('upgrade', (req, socket, head) => {
182
- try {
183
- const upgrade = (req.headers.upgrade || '').toLowerCase();
184
- if (upgrade !== 'websocket') {
185
- socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
186
- socket.destroy();
187
- return;
188
- }
189
- const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
190
- const password = url.searchParams.get('password') ?? '';
191
- if (password !== this.storedPassword) {
192
- this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
193
- socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
194
- socket.destroy();
195
- return;
196
- }
197
- this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
198
- if (req.socket.remoteAddress)
199
- this.authClients.add(req.socket.remoteAddress);
200
- this.backendWsServer?.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
201
- this.backendWsServer?.webSocketServer?.emit('connection', ws, req);
202
- });
203
- }
204
- catch (err) {
205
- {
206
- inspectError(this.log, 'WebSocket upgrade error:', err);
207
- socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
208
- socket.destroy();
209
- }
210
- }
211
- });
212
- this.httpsServer.on('error', (error) => {
213
- this.log.error(`Frontend https server error listening on ${this.port}`);
214
- switch (error.code) {
215
- case 'EACCES':
216
- this.log.error(`Port ${this.port} requires elevated privileges`);
217
- break;
218
- case 'EADDRINUSE':
219
- this.log.error(`Port ${this.port} is already in use`);
220
- break;
221
- }
222
- this.emit('server_error', error);
223
- });
224
- }
225
- else {
226
- const http = await import('node:http');
227
- await this.backendExpress.start();
228
- try {
229
- this.log.debug(`Creating HTTP server...`);
230
- this.httpServer = http.createServer(this.backendExpress.expressApp);
231
- }
232
- catch (error) {
233
- logError(this.log, `Failed to create HTTP server`, error);
234
- this.emit('server_error', error);
235
- await this.backendExpress.stop();
236
- return;
237
- }
238
- await this.backendWsServer.start();
239
- this.httpServer.listen(this.port, getParameter('bind'), () => {
240
- const addr = this.httpServer?.address();
241
- if (addr && typeof addr !== 'string') {
242
- this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
243
- }
244
- if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
245
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
246
- if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
247
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
248
- this.listening = true;
249
- this.emit('server_listening', 'http', this.port);
250
- });
251
- this.httpServer.on('upgrade', (req, socket, head) => {
252
- try {
253
- const upgrade = (req.headers.upgrade || '').toLowerCase();
254
- if (upgrade !== 'websocket') {
255
- this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
256
- socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
257
- socket.destroy();
258
- return;
259
- }
260
- const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
261
- const password = url.searchParams.get('password') ?? '';
262
- if (password !== this.storedPassword) {
263
- this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
264
- socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
265
- socket.destroy();
266
- return;
267
- }
268
- this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
269
- if (req.socket.remoteAddress)
270
- this.authClients.add(req.socket.remoteAddress);
271
- this.backendWsServer?.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
272
- this.backendWsServer?.webSocketServer?.emit('connection', ws, req);
273
- });
274
- }
275
- catch (err) {
276
- {
277
- inspectError(this.log, 'WebSocket upgrade error:', err);
278
- socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
279
- socket.destroy();
280
- }
281
- }
282
- return;
283
- });
284
- this.httpServer.on('error', (error) => {
285
- this.log.error(`Frontend http server error listening on ${this.port}`);
286
- switch (error.code) {
287
- case 'EACCES':
288
- this.log.error(`Port ${this.port} requires elevated privileges`);
289
- break;
290
- case 'EADDRINUSE':
291
- this.log.error(`Port ${this.port} is already in use`);
292
- break;
293
- }
294
- this.emit('server_error', error);
295
- });
296
- }
297
- this.log.debug('Backend started');
298
- }
299
- async stop() {
300
- this.log.debug('Stopping backend...');
301
- await this.backendWsServer?.stop();
302
- if (this.httpServer) {
303
- this.log.debug('Closing http server...');
304
- this.httpServer.close();
305
- this.log.debug('Http server closed successfully');
306
- this.listening = false;
307
- this.emit('server_stopped');
308
- this.httpServer.removeAllListeners();
309
- this.httpServer = undefined;
310
- this.log.debug('Backend http server closed successfully');
311
- }
312
- if (this.httpsServer) {
313
- this.log.debug('Closing https server...');
314
- this.httpsServer.close();
315
- this.log.debug('Https server closed successfully');
316
- this.listening = false;
317
- this.emit('server_stopped');
318
- this.httpsServer.removeAllListeners();
319
- this.httpsServer = undefined;
320
- this.log.debug('Backend https server closed successfully');
321
- }
322
- await this.backendExpress?.stop();
323
- this.backendWsServer?.destroy();
324
- this.backendExpress?.destroy();
325
- this.backendWsServer = undefined;
326
- this.backendExpress = undefined;
327
- this.log.debug('Backend stopped');
328
- }
329
- async getApiSettings() {
330
- const start = performance.now();
331
- const response = await this.server.fetch({ type: 'matterbridge_apisettings', src: 'frontend', dst: 'matterbridge', params: undefined });
332
- if (this.diagnostic)
333
- writeDiagnostic('Backend', `getApiSettings() took ${(performance.now() - start).toFixed(2)} ms`);
334
- return response.result.data;
335
- }
336
- async getApiPlugins() {
337
- const start = performance.now();
338
- const response = await this.server.fetch({ type: 'plugins_apipluginarray', src: 'frontend', dst: 'plugins', params: undefined });
339
- if (this.diagnostic)
340
- writeDiagnostic('Backend', `getApiPlugins() took ${(performance.now() - start).toFixed(2)} ms`);
341
- return response.result.plugins;
342
- }
343
- async getApiDevices(pluginName) {
344
- const start = performance.now();
345
- const response = await this.server.fetch({ type: 'devices_apidevicearray', src: 'frontend', dst: 'devices', params: { pluginName } });
346
- if (this.diagnostic)
347
- writeDiagnostic('Backend', `getApiDevices() took ${(performance.now() - start).toFixed(2)} ms`);
348
- return response.result.devices;
349
- }
350
- async getApiCluster(pluginName, endpointNumber, serialNumber, uniqueId) {
351
- const start = performance.now();
352
- this.log.debug(`Retrieving API cluster for plugin: ${pluginName}, endpoint: ${endpointNumber}, serial: ${serialNumber}, uniqueId: ${uniqueId}`);
353
- if (this.diagnostic)
354
- writeDiagnostic('Backend', `getApiCluster() took ${(performance.now() - start).toFixed(2)} ms`);
355
- return undefined;
356
- }
357
- async generateDiagnostic() {
358
- const start = performance.now();
359
- if (this.diagnostic)
360
- writeDiagnostic('Backend', `generateDiagnostic() took ${(performance.now() - start).toFixed(2)} ms`);
361
- }
362
- async getApiMatter(id) {
363
- const start = performance.now();
364
- const response = await this.server.fetch({ type: 'matterbridge_apimatter', src: 'frontend', dst: 'matterbridge', params: { id } });
365
- if (this.diagnostic)
366
- writeDiagnostic('Backend', `getApiMatter() took ${(performance.now() - start).toFixed(2)} ms`);
367
- return response.result.matter;
368
- }
369
- }
@@ -1,21 +0,0 @@
1
- import { type SharedMatterbridge } from '@matterbridge/types';
2
- import express from 'express';
3
- import type { Backend } from './backend.js';
4
- export declare class BackendExpress {
5
- private debug;
6
- private verbose;
7
- private diagnostic;
8
- private log;
9
- private backend;
10
- private matterbridge;
11
- private readonly server;
12
- private fileLimiter;
13
- expressApp: express.Application | undefined;
14
- serverFetchTimeout: number;
15
- constructor(matterbridge: SharedMatterbridge, backend: Backend);
16
- destroy(): void;
17
- private broadcastMsgHandler;
18
- private validateReq;
19
- start(): Promise<express.Application | undefined>;
20
- stop(): Promise<express.Application | undefined>;
21
- }