@matterbridge/core 3.8.0-dev-20260527-b4148f4 → 3.8.0-dev-20260528-1971f9d

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.
@@ -0,0 +1,37 @@
1
+ import EventEmitter from 'node:events';
2
+ import type { ApiDevice, ApiPlugin, ApiSettings, SharedMatterbridge } from '@matterbridge/types';
3
+ import type { BackendExpress } from './backendExpress.js';
4
+ import type { BackendWsServer } from './backendWsServer.js';
5
+ interface BackendEvents {
6
+ server_listening: [protocol: string, port: number, address?: string];
7
+ server_error: [error: unknown];
8
+ server_stopped: [];
9
+ websocket_server_listening: [protocol: string];
10
+ websocket_server_stopped: [];
11
+ }
12
+ export declare class Backend extends EventEmitter<BackendEvents> {
13
+ private debug;
14
+ private verbose;
15
+ private log;
16
+ private matterbridge;
17
+ private readonly server;
18
+ private port;
19
+ private listening;
20
+ private httpServer;
21
+ private httpsServer;
22
+ backendExpress: BackendExpress | undefined;
23
+ backendWsServer: BackendWsServer | undefined;
24
+ storedPassword: string | undefined;
25
+ authClients: Set<string>;
26
+ authClientsTimeout: NodeJS.Timeout | undefined;
27
+ constructor(matterbridge: SharedMatterbridge);
28
+ destroy(): void;
29
+ private broadcastMsgHandler;
30
+ start(port?: number): Promise<void>;
31
+ stop(): Promise<void>;
32
+ getApiSettings(): ApiSettings;
33
+ getApiPlugins(): ApiPlugin[];
34
+ getApiDevices(_pluginName?: string): ApiDevice[];
35
+ generateDiagnostic(): Promise<void>;
36
+ }
37
+ export {};
@@ -0,0 +1,294 @@
1
+ import EventEmitter from 'node:events';
2
+ import path from 'node:path';
3
+ import { BroadcastServer } from '@matterbridge/thread';
4
+ import { getParameter, hasParameter } from '@matterbridge/utils/cli';
5
+ import { getErrorMessage, inspectError, logError } from '@matterbridge/utils/error';
6
+ import { AnsiLogger, rs, UNDERLINE, UNDERLINEOFF } from 'node-ansi-logger';
7
+ if (hasParameter('loader'))
8
+ console.log('\u001B[32mBackend loaded.\u001B[40;0m');
9
+ export class Backend extends EventEmitter {
10
+ debug;
11
+ verbose;
12
+ log;
13
+ matterbridge;
14
+ server;
15
+ port = 8283;
16
+ listening = false;
17
+ httpServer;
18
+ httpsServer;
19
+ backendExpress;
20
+ backendWsServer;
21
+ storedPassword = undefined;
22
+ authClients = new Set();
23
+ authClientsTimeout = undefined;
24
+ constructor(matterbridge) {
25
+ super();
26
+ this.debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-frontend') || hasParameter('verbose-frontend');
27
+ this.verbose = hasParameter('verbose') || hasParameter('verbose-frontend');
28
+ this.matterbridge = matterbridge;
29
+ this.log = new AnsiLogger({
30
+ logName: 'Backend',
31
+ logNameColor: '\x1b[38;5;97m',
32
+ logTimestampFormat: 4,
33
+ logLevel: this.debug ? "debug" : "info",
34
+ });
35
+ this.server = new BroadcastServer('frontend', this.log);
36
+ this.server.on('broadcast_message', this.broadcastMsgHandler.bind(this));
37
+ }
38
+ destroy() {
39
+ this.server.off('broadcast_message', this.broadcastMsgHandler.bind(this));
40
+ this.server.close();
41
+ }
42
+ async broadcastMsgHandler(msg) {
43
+ if (this.server.isWorkerRequest(msg)) {
44
+ switch (msg.type) {
45
+ case 'get_log_level':
46
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
47
+ break;
48
+ case 'set_log_level':
49
+ this.log.logLevel = msg.params.logLevel;
50
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ async start(port = 8283) {
56
+ this.log.debug('Starting backend...');
57
+ this.port = port;
58
+ const { BackendExpress } = await import('./backendExpress.js');
59
+ const { BackendWsServer } = await import('./backendWsServer.js');
60
+ this.backendExpress = new BackendExpress(this.matterbridge, this);
61
+ this.backendWsServer = new BackendWsServer(this.matterbridge, this);
62
+ if (!hasParameter('ssl')) {
63
+ const http = await import('node:http');
64
+ try {
65
+ this.log.debug(`Creating HTTP server...`);
66
+ this.httpServer = http.createServer(this.backendExpress.expressApp);
67
+ }
68
+ catch (error) {
69
+ logError(this.log, `Failed to create HTTP server`, error);
70
+ this.emit('server_error', error);
71
+ return;
72
+ }
73
+ this.httpServer.listen(this.port, getParameter('bind'), () => {
74
+ const addr = this.httpServer?.address();
75
+ if (addr && typeof addr !== 'string') {
76
+ this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
77
+ }
78
+ if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
79
+ this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
80
+ if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
81
+ this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
82
+ this.listening = true;
83
+ this.emit('server_listening', 'http', this.port);
84
+ });
85
+ this.httpServer.on('upgrade', (req, socket, head) => {
86
+ try {
87
+ if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
88
+ this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
89
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
90
+ return socket.destroy();
91
+ }
92
+ const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
93
+ const password = url.searchParams.get('password') ?? '';
94
+ if (password !== this.storedPassword) {
95
+ this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
96
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
97
+ return socket.destroy();
98
+ }
99
+ this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
100
+ if (req.socket.remoteAddress)
101
+ this.authClients.add(req.socket.remoteAddress);
102
+ this.backendWsServer?.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
103
+ this.backendWsServer?.webSocketServer?.emit('connection', ws, req);
104
+ });
105
+ }
106
+ catch (err) {
107
+ {
108
+ inspectError(this.log, 'WebSocket upgrade error:', err);
109
+ socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
110
+ socket.destroy();
111
+ }
112
+ }
113
+ return undefined;
114
+ });
115
+ this.httpServer.on('error', (error) => {
116
+ this.log.error(`Frontend http server error listening on ${this.port}`);
117
+ switch (error.code) {
118
+ case 'EACCES':
119
+ this.log.error(`Port ${this.port} requires elevated privileges`);
120
+ break;
121
+ case 'EADDRINUSE':
122
+ this.log.error(`Port ${this.port} is already in use`);
123
+ break;
124
+ }
125
+ this.emit('server_error', error);
126
+ return;
127
+ });
128
+ }
129
+ else {
130
+ let cert;
131
+ let key;
132
+ let ca;
133
+ let fullChain;
134
+ let pfx;
135
+ let passphrase;
136
+ let httpsServerOptions;
137
+ const fs = await import('node:fs');
138
+ if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
139
+ try {
140
+ pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
141
+ this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
142
+ }
143
+ catch (error) {
144
+ logError(this.log, `Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`, error);
145
+ this.emit('server_error', error);
146
+ return;
147
+ }
148
+ try {
149
+ passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
150
+ passphrase = passphrase.trim();
151
+ this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
152
+ }
153
+ catch (error) {
154
+ logError(this.log, `Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`, error);
155
+ this.emit('server_error', error);
156
+ return;
157
+ }
158
+ httpsServerOptions = { pfx, passphrase };
159
+ }
160
+ else {
161
+ try {
162
+ cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
163
+ this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
164
+ }
165
+ catch (error) {
166
+ logError(this.log, `Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`, error);
167
+ this.emit('server_error', error);
168
+ return;
169
+ }
170
+ try {
171
+ key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem'), 'utf8');
172
+ this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}`);
173
+ }
174
+ catch (error) {
175
+ logError(this.log, `Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}`, error);
176
+ this.emit('server_error', error);
177
+ return;
178
+ }
179
+ try {
180
+ ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem'), 'utf8');
181
+ fullChain = `${cert}\n${ca}`;
182
+ this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')}`);
183
+ }
184
+ catch (error) {
185
+ this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')} not loaded: ${getErrorMessage(error)}`);
186
+ }
187
+ httpsServerOptions = { cert: fullChain ?? cert, key, ca };
188
+ }
189
+ if (hasParameter('mtls')) {
190
+ httpsServerOptions.requestCert = true;
191
+ httpsServerOptions.rejectUnauthorized = true;
192
+ }
193
+ const https = await import('node:https');
194
+ try {
195
+ this.log.debug(`Creating HTTPS server...`);
196
+ this.httpsServer = https.createServer(httpsServerOptions, this.backendExpress.expressApp);
197
+ }
198
+ catch (error) {
199
+ logError(this.log, `Failed to create HTTPS server`, error);
200
+ this.emit('server_error', error);
201
+ return;
202
+ }
203
+ this.httpsServer.listen(this.port, getParameter('bind'), () => {
204
+ const addr = this.httpsServer?.address();
205
+ if (addr && typeof addr !== 'string') {
206
+ this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
207
+ }
208
+ if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
209
+ this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
210
+ if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
211
+ this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
212
+ this.listening = true;
213
+ this.emit('server_listening', 'https', this.port);
214
+ });
215
+ this.httpsServer.on('upgrade', (req, socket, head) => {
216
+ try {
217
+ if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
218
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
219
+ return socket.destroy();
220
+ }
221
+ const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
222
+ const password = url.searchParams.get('password') ?? '';
223
+ if (password !== this.storedPassword) {
224
+ this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
225
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
226
+ return socket.destroy();
227
+ }
228
+ this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
229
+ if (req.socket.remoteAddress)
230
+ this.authClients.add(req.socket.remoteAddress);
231
+ this.backendWsServer?.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
232
+ this.backendWsServer?.webSocketServer?.emit('connection', ws, req);
233
+ });
234
+ }
235
+ catch (err) {
236
+ {
237
+ inspectError(this.log, 'WebSocket upgrade error:', err);
238
+ socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
239
+ socket.destroy();
240
+ }
241
+ }
242
+ return undefined;
243
+ });
244
+ this.httpsServer.on('error', (error) => {
245
+ this.log.error(`Frontend https server error listening on ${this.port}`);
246
+ switch (error.code) {
247
+ case 'EACCES':
248
+ this.log.error(`Port ${this.port} requires elevated privileges`);
249
+ break;
250
+ case 'EADDRINUSE':
251
+ this.log.error(`Port ${this.port} is already in use`);
252
+ break;
253
+ }
254
+ this.emit('server_error', error);
255
+ return;
256
+ });
257
+ }
258
+ this.log.debug('Backend started');
259
+ }
260
+ async stop() {
261
+ this.log.debug('Stopping backend...');
262
+ if (this.httpServer) {
263
+ this.log.debug('Closing http server...');
264
+ this.httpServer.close();
265
+ this.log.debug('Http server closed successfully');
266
+ this.listening = false;
267
+ this.emit('server_stopped');
268
+ this.httpServer.removeAllListeners();
269
+ this.httpServer = undefined;
270
+ this.log.debug('Backend http server closed successfully');
271
+ }
272
+ if (this.httpsServer) {
273
+ this.log.debug('Closing https server...');
274
+ this.httpsServer.close();
275
+ this.log.debug('Https server closed successfully');
276
+ this.listening = false;
277
+ this.emit('server_stopped');
278
+ this.httpsServer.removeAllListeners();
279
+ this.httpsServer = undefined;
280
+ this.log.debug('Backend https server closed successfully');
281
+ }
282
+ this.log.debug('Backend stopped');
283
+ }
284
+ getApiSettings() {
285
+ return {};
286
+ }
287
+ getApiPlugins() {
288
+ return [];
289
+ }
290
+ getApiDevices(_pluginName) {
291
+ return [];
292
+ }
293
+ async generateDiagnostic() { }
294
+ }
@@ -1,16 +1,16 @@
1
1
  import { type SharedMatterbridge } from '@matterbridge/types';
2
2
  import express from 'express';
3
- import type { Frontend } from './frontend.js';
3
+ import { Backend } from './backend.js';
4
4
  export declare class BackendExpress {
5
5
  private debug;
6
6
  private verbose;
7
- private expressApp;
8
7
  private log;
9
8
  private backend;
10
9
  private matterbridge;
11
10
  private readonly server;
12
11
  private fileLimiter;
13
- constructor(matterbridge: SharedMatterbridge, backend: Frontend);
12
+ expressApp: express.Application | undefined;
13
+ constructor(matterbridge: SharedMatterbridge, backend: Backend);
14
14
  destroy(): void;
15
15
  private broadcastMsgHandler;
16
16
  private validateReq;
@@ -15,7 +15,6 @@ if (hasParameter('loader'))
15
15
  export class BackendExpress {
16
16
  debug;
17
17
  verbose;
18
- expressApp;
19
18
  log;
20
19
  backend;
21
20
  matterbridge;
@@ -24,6 +23,7 @@ export class BackendExpress {
24
23
  windowMs: 60 * 1000,
25
24
  max: 20,
26
25
  });
26
+ expressApp;
27
27
  constructor(matterbridge, backend) {
28
28
  this.debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-frontend') || hasParameter('verbose-frontend');
29
29
  this.verbose = hasParameter('verbose') || hasParameter('verbose-frontend');
@@ -111,7 +111,7 @@ export class BackendExpress {
111
111
  const { default: v8 } = await import('node:v8');
112
112
  const heapStatsRaw = v8.getHeapStatistics();
113
113
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
114
- const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
114
+ const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, typeof value === 'number' ? formatBytes(value) : value]));
115
115
  const heapSpaces = heapSpacesRaw.map((space) => ({
116
116
  ...space,
117
117
  space_size: formatBytes(space.space_size),
@@ -314,7 +314,7 @@ export class BackendExpress {
314
314
  if (!this.validateReq(req, res))
315
315
  return;
316
316
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_BACKUP_FILE), MATTERBRIDGE_BACKUP_FILE, (error) => {
317
- this.backend.wssSendCloseSnackbarMessage('Creating matterbridge backup...');
317
+ this.backend.backendWsServer?.wssSendCloseSnackbarMessage('Creating matterbridge backup...');
318
318
  if (error) {
319
319
  this.log.error(`Error downloading file ${MATTERBRIDGE_BACKUP_FILE}: ${getErrorMessage(error)}`);
320
320
  res.status(500).send(`Error downloading the matterbridge backup file.`);
@@ -329,9 +329,9 @@ export class BackendExpress {
329
329
  if (!this.validateReq(req, res))
330
330
  return;
331
331
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
332
- this.backend.wssSendCloseSnackbarMessage('Creating matterbridge storage backup...');
332
+ this.backend.backendWsServer?.wssSendCloseSnackbarMessage('Creating matterbridge storage backup...');
333
333
  if (error) {
334
- this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${getErrorMessage(error)}`);
334
+ this.log.error(`Error downloading file matterbridge.${NODE_STORAGE_DIR}.zip: ${getErrorMessage(error)}`);
335
335
  res.status(500).send('Error downloading the matterbridge storage file');
336
336
  }
337
337
  else {
@@ -344,7 +344,7 @@ export class BackendExpress {
344
344
  if (!this.validateReq(req, res))
345
345
  return;
346
346
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_DIR}.zip`), `matterbridge.${MATTER_STORAGE_DIR}.zip`, (error) => {
347
- this.backend.wssSendCloseSnackbarMessage('Creating matter storage backup...');
347
+ this.backend.backendWsServer?.wssSendCloseSnackbarMessage('Creating matter storage backup...');
348
348
  if (error) {
349
349
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_DIR}.zip: ${getErrorMessage(error)}`);
350
350
  res.status(500).send('Error downloading the matter storage file');
@@ -359,7 +359,7 @@ export class BackendExpress {
359
359
  if (!this.validateReq(req, res))
360
360
  return;
361
361
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_PLUGIN_STORAGE_FILE), MATTERBRIDGE_PLUGIN_STORAGE_FILE, (error) => {
362
- this.backend.wssSendCloseSnackbarMessage('Creating plugin backup...');
362
+ this.backend.backendWsServer?.wssSendCloseSnackbarMessage('Creating plugin backup...');
363
363
  if (error) {
364
364
  this.log.error(`Error downloading file ${MATTERBRIDGE_PLUGIN_STORAGE_FILE}: ${getErrorMessage(error)}`);
365
365
  res.status(500).send('Error downloading the matterbridge plugin storage file');
@@ -374,7 +374,7 @@ export class BackendExpress {
374
374
  if (!this.validateReq(req, res))
375
375
  return;
376
376
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_PLUGIN_CONFIG_FILE), MATTERBRIDGE_PLUGIN_CONFIG_FILE, (error) => {
377
- this.backend.wssSendCloseSnackbarMessage('Creating config backup...');
377
+ this.backend.backendWsServer?.wssSendCloseSnackbarMessage('Creating config backup...');
378
378
  if (error) {
379
379
  this.log.error(`Error downloading file ${MATTERBRIDGE_PLUGIN_CONFIG_FILE}: ${getErrorMessage(error)}`);
380
380
  res.status(500).send('Error downloading the matterbridge plugin config file');
@@ -395,7 +395,7 @@ export class BackendExpress {
395
395
  res.status(400).send('Invalid request: file and filename are required');
396
396
  return;
397
397
  }
398
- this.backend.wssSendSnackbarMessage(`Installing package ${filename}...`, 0);
398
+ this.backend.backendWsServer?.wssSendSnackbarMessage(`Installing package ${filename}...`, 0);
399
399
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
400
400
  try {
401
401
  const fs = await import('node:fs');
@@ -422,8 +422,8 @@ export class BackendExpress {
422
422
  }
423
423
  catch (err) {
424
424
  this.log.error(`Error uploading or installing plugin package file ${plg}${filename}${er}:`, err);
425
- this.backend.wssSendCloseSnackbarMessage(`Installing package ${filename}...`);
426
- this.backend.wssSendSnackbarMessage(`Error uploading or installing plugin package ${filename}`, 10, 'error');
425
+ this.backend.backendWsServer?.wssSendCloseSnackbarMessage(`Installing package ${filename}...`);
426
+ this.backend.backendWsServer?.wssSendSnackbarMessage(`Error uploading or installing plugin package ${filename}`, 10, 'error');
427
427
  res.status(500).send(`Error uploading or installing plugin package ${escapeHtml(filename)}`);
428
428
  }
429
429
  });
@@ -1,16 +1,16 @@
1
1
  import type { EndpointNumber } from '@matter/types/datatype';
2
2
  import type { ApiMatter, RefreshRequiredChanged, SharedMatterbridge, WsMessageBroadcast } from '@matterbridge/types';
3
3
  import { WebSocketServer } from 'ws';
4
- import type { Frontend } from './frontend.js';
5
- export declare class BackendsWsServer {
4
+ import { Backend } from './backend.js';
5
+ export declare class BackendWsServer {
6
6
  private debug;
7
7
  private verbose;
8
- private webSocketServer;
9
8
  private log;
10
9
  private backend;
11
10
  private matterbridge;
12
11
  private readonly server;
13
- constructor(matterbridge: SharedMatterbridge, backend: Frontend);
12
+ webSocketServer: WebSocketServer | undefined;
13
+ constructor(matterbridge: SharedMatterbridge, backend: Backend);
14
14
  destroy(): void;
15
15
  private broadcastMsgHandler;
16
16
  start(): Promise<WebSocketServer | undefined>;
@@ -8,14 +8,14 @@ import { AnsiLogger, CYAN, debugStringify, nf } from 'node-ansi-logger';
8
8
  import { WebSocket, WebSocketServer } from 'ws';
9
9
  if (hasParameter('loader'))
10
10
  console.log('\u001B[32mBackendWsServer loaded.\u001B[40;0m');
11
- export class BackendsWsServer {
11
+ export class BackendWsServer {
12
12
  debug;
13
13
  verbose;
14
- webSocketServer;
15
14
  log;
16
15
  backend;
17
16
  matterbridge;
18
17
  server;
18
+ webSocketServer;
19
19
  constructor(matterbridge, backend) {
20
20
  this.debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-frontend') || hasParameter('verbose-frontend');
21
21
  this.verbose = hasParameter('verbose') || hasParameter('verbose-frontend');
@@ -149,7 +149,8 @@ export class BackendsWsServer {
149
149
  }
150
150
  };
151
151
  try {
152
- data = JSON.parse(rawData.toString());
152
+ const raw = Array.isArray(rawData) ? Buffer.concat(rawData) : rawData instanceof ArrayBuffer ? Buffer.from(rawData) : rawData;
153
+ data = JSON.parse(raw.toString());
153
154
  if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
154
155
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
155
156
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
@@ -217,9 +218,7 @@ export class BackendsWsServer {
217
218
  return;
218
219
  if (this.verbose)
219
220
  this.log.debug('Sending a restart required message to all connected clients');
220
- this.backend.restartRequired = true;
221
- this.backend.fixedRestartRequired = fixed;
222
- if (snackbar === true)
221
+ if (snackbar)
223
222
  this.wssSendSnackbarMessage(`Restart required`, 0);
224
223
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
225
224
  }
@@ -228,8 +227,7 @@ export class BackendsWsServer {
228
227
  return;
229
228
  if (this.verbose)
230
229
  this.log.debug('Sending a restart not required message to all connected clients');
231
- this.backend.restartRequired = false;
232
- if (snackbar === true)
230
+ if (snackbar)
233
231
  this.wssSendCloseSnackbarMessage(`Restart required`);
234
232
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
235
233
  }
@@ -238,7 +236,6 @@ export class BackendsWsServer {
238
236
  return;
239
237
  if (this.verbose)
240
238
  this.log.debug('Sending an update required message to all connected clients');
241
- this.backend.updateRequired = true;
242
239
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
243
240
  }
244
241
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
@@ -1,9 +1,9 @@
1
1
  import { ActivatedCarbonFilterMonitoringServer } from '@matter/node/behaviors/activated-carbon-filter-monitoring';
2
2
  import { ActivatedCarbonFilterMonitoring } from '@matter/types/clusters/activated-carbon-filter-monitoring';
3
3
  declare const MatterbridgeActivatedCarbonFilterMonitoringServer_base: import("@matter/node").ClusterBehavior.Type<typeof ActivatedCarbonFilterMonitoringServer, import("@matter/types").ClusterType.WithSupportedFeatures<ActivatedCarbonFilterMonitoring, {
4
+ warning: false;
4
5
  condition: true;
5
6
  replacementProductList: false;
6
- warning: false;
7
7
  }>, import("@matter/types").ClusterType.Concrete, new () => {}, "activatedCarbonFilterMonitoring">;
8
8
  export declare class MatterbridgeActivatedCarbonFilterMonitoringServer extends MatterbridgeActivatedCarbonFilterMonitoringServer_base {
9
9
  resetCondition(): Promise<void>;
@@ -1,9 +1,9 @@
1
1
  import { HepaFilterMonitoringServer } from '@matter/node/behaviors/hepa-filter-monitoring';
2
2
  import { HepaFilterMonitoring } from '@matter/types/clusters/hepa-filter-monitoring';
3
3
  declare const MatterbridgeHepaFilterMonitoringServer_base: import("@matter/node").ClusterBehavior.Type<typeof HepaFilterMonitoringServer, import("@matter/types").ClusterType.WithSupportedFeatures<HepaFilterMonitoring, {
4
+ warning: false;
4
5
  condition: true;
5
6
  replacementProductList: false;
6
- warning: false;
7
7
  }>, import("@matter/types").ClusterType.Concrete, new () => {}, "hepaFilterMonitoring">;
8
8
  export declare class MatterbridgeHepaFilterMonitoringServer extends MatterbridgeHepaFilterMonitoringServer_base {
9
9
  resetCondition(): Promise<void>;
@@ -22,6 +22,7 @@ import { toBaseDevice } from './deviceManager.js';
22
22
  import { addVirtualDevice } from './helpers.js';
23
23
  import { bridge } from './matterbridgeDeviceTypes.js';
24
24
  import { PluginManager } from './pluginManager.js';
25
+ import { getErrorMessage } from './utils/export.js';
25
26
  export class MatterNode extends EventEmitter {
26
27
  matterbridge;
27
28
  pluginName;
@@ -515,7 +516,7 @@ export class MatterNode extends EventEmitter {
515
516
  this.log.notice(`Started ${this.serverNode.id} server node`);
516
517
  }
517
518
  catch (error) {
518
- this.log.error(`Failed to start ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
519
+ this.log.error(`Failed to start ${this.serverNode.id} server node: ${getErrorMessage(error)}`);
519
520
  }
520
521
  }
521
522
  async stopServerNode(timeout = 30000) {
@@ -528,7 +529,7 @@ export class MatterNode extends EventEmitter {
528
529
  this.log.info(`Closed ${this.serverNode.id} server node`);
529
530
  }
530
531
  catch (error) {
531
- this.log.error(`Failed to close ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
532
+ this.log.error(`Failed to close ${this.serverNode.id} server node: ${getErrorMessage(error)}`);
532
533
  }
533
534
  }
534
535
  async createAggregatorNode() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.8.0-dev-20260527-b4148f4",
3
+ "version": "3.8.0-dev-20260528-1971f9d",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -130,10 +130,10 @@
130
130
  ],
131
131
  "dependencies": {
132
132
  "@matter/main": "0.17.0",
133
- "@matterbridge/dgram": "3.8.0-dev-20260527-b4148f4",
134
- "@matterbridge/thread": "3.8.0-dev-20260527-b4148f4",
135
- "@matterbridge/types": "3.8.0-dev-20260527-b4148f4",
136
- "@matterbridge/utils": "3.8.0-dev-20260527-b4148f4",
133
+ "@matterbridge/dgram": "3.8.0-dev-20260528-1971f9d",
134
+ "@matterbridge/thread": "3.8.0-dev-20260528-1971f9d",
135
+ "@matterbridge/types": "3.8.0-dev-20260528-1971f9d",
136
+ "@matterbridge/utils": "3.8.0-dev-20260528-1971f9d",
137
137
  "escape-html": "1.0.3",
138
138
  "express": "5.2.1",
139
139
  "express-rate-limit": "8.5.2",