matterbridge 3.3.5-dev-20251025-26d5c31 → 3.3.5-dev-20251028-d89f93f

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/CHANGELOG.md CHANGED
@@ -30,9 +30,13 @@ Advantages:
30
30
 
31
31
  ## [3.3.5] - 2025-10-??
32
32
 
33
+ - [thread]: Added get_log_level and set_log_level to BroadcastServer.
34
+ - [frontend]: Added password check to WebSocket.
35
+
33
36
  ### Changed
34
37
 
35
38
  - [package]: Updated dependencies.
39
+ - [frontend]: Bumped `frontend` version to 3.2.4.
36
40
 
37
41
  <a href="https://www.buymeacoffee.com/luligugithub">
38
42
  <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
@@ -20,6 +20,13 @@ export class DeviceManager {
20
20
  if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'devices')) {
21
21
  this.log.debug(`**Received request message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
22
22
  switch (msg.type) {
23
+ case 'get_log_level':
24
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
25
+ break;
26
+ case 'set_log_level':
27
+ this.log.logLevel = msg.params.logLevel;
28
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
29
+ break;
23
30
  case 'devices_length':
24
31
  this.server.respond({ ...msg, response: { length: this.length } });
25
32
  break;
package/dist/frontend.js CHANGED
@@ -26,6 +26,7 @@ export class Frontend extends EventEmitter {
26
26
  log;
27
27
  port = 8283;
28
28
  listening = false;
29
+ storedPassword = undefined;
29
30
  expressApp;
30
31
  httpServer;
31
32
  httpsServer;
@@ -46,6 +47,13 @@ export class Frontend extends EventEmitter {
46
47
  if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'frontend')) {
47
48
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
48
49
  switch (msg.type) {
50
+ case 'get_log_level':
51
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
52
+ break;
53
+ case 'set_log_level':
54
+ this.log.logLevel = msg.params.logLevel;
55
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
56
+ break;
49
57
  case 'frontend_start':
50
58
  await this.start(msg.params.port);
51
59
  this.server.respond({ ...msg, response: { success: true } });
@@ -54,6 +62,30 @@ export class Frontend extends EventEmitter {
54
62
  await this.stop();
55
63
  this.server.respond({ ...msg, response: { success: true } });
56
64
  break;
65
+ case 'frontend_refreshrequired':
66
+ this.wssSendRefreshRequired(msg.params.changed, { matter: msg.params.matter });
67
+ this.server.respond({ ...msg, response: { success: true } });
68
+ break;
69
+ case 'frontend_restartrequired':
70
+ this.wssSendRestartRequired(msg.params.snackbar, msg.params.fixed);
71
+ this.server.respond({ ...msg, response: { success: true } });
72
+ break;
73
+ case 'frontend_restartnotrequired':
74
+ this.wssSendRestartNotRequired(msg.params.snackbar);
75
+ this.server.respond({ ...msg, response: { success: true } });
76
+ break;
77
+ case 'frontend_updaterequired':
78
+ this.wssSendUpdateRequired(msg.params.devVersion);
79
+ this.server.respond({ ...msg, response: { success: true } });
80
+ break;
81
+ case 'frontend_snackbarmessage':
82
+ this.wssSendSnackbarMessage(msg.params.message, msg.params.timeout, msg.params.severity);
83
+ this.server.respond({ ...msg, response: { success: true } });
84
+ break;
85
+ case 'frontend_attributechanged':
86
+ this.wssSendAttributeChangedMessage(msg.params.plugin, msg.params.serialNumber, msg.params.uniqueId, msg.params.number, msg.params.id, msg.params.cluster, msg.params.attribute, msg.params.value);
87
+ this.server.respond({ ...msg, response: { success: true } });
88
+ break;
57
89
  default:
58
90
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
59
91
  }
@@ -61,6 +93,9 @@ export class Frontend extends EventEmitter {
61
93
  if (this.server.isWorkerResponse(msg, msg.type)) {
62
94
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
63
95
  switch (msg.type) {
96
+ case 'get_log_level':
97
+ case 'set_log_level':
98
+ break;
64
99
  case 'plugins_install':
65
100
  this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
66
101
  if (msg.response.success) {
@@ -93,6 +128,7 @@ export class Frontend extends EventEmitter {
93
128
  }
94
129
  async start(port = 8283) {
95
130
  this.port = port;
131
+ this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
96
132
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
97
133
  const multer = await import('multer');
98
134
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
@@ -100,6 +136,47 @@ export class Frontend extends EventEmitter {
100
136
  const express = await import('express');
101
137
  this.expressApp = express.default();
102
138
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
139
+ this.log.debug(`Creating WebSocketServer...`);
140
+ const ws = await import('ws');
141
+ this.webSocketServer = new ws.WebSocketServer({ noServer: true });
142
+ this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
143
+ this.webSocketServer.on('connection', (ws, request) => {
144
+ const clientIp = request.socket.remoteAddress;
145
+ let callbackLogLevel = "notice";
146
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
147
+ callbackLogLevel = "info";
148
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
149
+ callbackLogLevel = "debug";
150
+ AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
151
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
152
+ this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
153
+ ws.on('message', (message) => {
154
+ this.wsMessageHandler(ws, message);
155
+ });
156
+ ws.on('ping', () => {
157
+ this.log.debug('WebSocket client ping');
158
+ ws.pong();
159
+ });
160
+ ws.on('pong', () => {
161
+ this.log.debug('WebSocket client pong');
162
+ });
163
+ ws.on('close', () => {
164
+ this.log.info('WebSocket client disconnected');
165
+ if (this.webSocketServer?.clients.size === 0) {
166
+ AnsiLogger.setGlobalCallback(undefined);
167
+ this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
168
+ }
169
+ });
170
+ ws.on('error', (error) => {
171
+ this.log.error(`WebSocket client error: ${error}`);
172
+ });
173
+ });
174
+ this.webSocketServer.on('close', () => {
175
+ this.log.debug(`WebSocketServer closed`);
176
+ });
177
+ this.webSocketServer.on('error', (ws, error) => {
178
+ this.log.error(`WebSocketServer error: ${error}`);
179
+ });
103
180
  if (!hasParameter('ssl')) {
104
181
  const http = await import('node:http');
105
182
  try {
@@ -128,6 +205,32 @@ export class Frontend extends EventEmitter {
128
205
  this.emit('server_listening', 'http', this.port);
129
206
  });
130
207
  }
208
+ this.httpServer.on('upgrade', async (req, socket, head) => {
209
+ try {
210
+ if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
211
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
212
+ return socket.destroy();
213
+ }
214
+ const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
215
+ const password = url.searchParams.get('password') ?? '';
216
+ if (password !== this.storedPassword) {
217
+ this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
218
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
219
+ return socket.destroy();
220
+ }
221
+ this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
222
+ this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
223
+ this.webSocketServer?.emit('connection', ws, req);
224
+ });
225
+ }
226
+ catch (err) {
227
+ {
228
+ inspectError(this.log, 'WebSocket upgrade error:', err);
229
+ socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
230
+ socket.destroy();
231
+ }
232
+ }
233
+ });
131
234
  this.httpServer.on('error', (error) => {
132
235
  this.log.error(`Frontend http server error listening on ${this.port}`);
133
236
  switch (error.code) {
@@ -233,6 +336,32 @@ export class Frontend extends EventEmitter {
233
336
  this.emit('server_listening', 'https', this.port);
234
337
  });
235
338
  }
339
+ this.httpsServer.on('upgrade', async (req, socket, head) => {
340
+ try {
341
+ if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
342
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
343
+ return socket.destroy();
344
+ }
345
+ const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
346
+ const password = url.searchParams.get('password') ?? '';
347
+ if (password !== this.storedPassword) {
348
+ this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
349
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
350
+ return socket.destroy();
351
+ }
352
+ this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
353
+ this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
354
+ this.webSocketServer?.emit('connection', ws, req);
355
+ });
356
+ }
357
+ catch (err) {
358
+ {
359
+ inspectError(this.log, 'WebSocket upgrade error:', err);
360
+ socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
361
+ socket.destroy();
362
+ }
363
+ }
364
+ });
236
365
  this.httpsServer.on('error', (error) => {
237
366
  this.log.error(`Frontend https server error listening on ${this.port}`);
238
367
  switch (error.code) {
@@ -247,50 +376,6 @@ export class Frontend extends EventEmitter {
247
376
  return;
248
377
  });
249
378
  }
250
- const ws = await import('ws');
251
- this.log.debug(`Creating WebSocketServer...`);
252
- this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
253
- this.webSocketServer.on('connection', (ws, request) => {
254
- const clientIp = request.socket.remoteAddress;
255
- let callbackLogLevel = "notice";
256
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
257
- callbackLogLevel = "info";
258
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
259
- callbackLogLevel = "debug";
260
- AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
261
- this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
262
- this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
263
- ws.on('message', (message) => {
264
- this.wsMessageHandler(ws, message);
265
- });
266
- ws.on('ping', () => {
267
- this.log.debug('WebSocket client ping');
268
- ws.pong();
269
- });
270
- ws.on('pong', () => {
271
- this.log.debug('WebSocket client pong');
272
- });
273
- ws.on('close', () => {
274
- this.log.info('WebSocket client disconnected');
275
- if (this.webSocketServer?.clients.size === 0) {
276
- AnsiLogger.setGlobalCallback(undefined);
277
- this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
278
- }
279
- });
280
- ws.on('error', (error) => {
281
- this.log.error(`WebSocket client error: ${error}`);
282
- });
283
- });
284
- this.webSocketServer.on('close', () => {
285
- this.log.debug(`WebSocketServer closed`);
286
- });
287
- this.webSocketServer.on('listening', () => {
288
- this.log.info(`The WebSocketServer is listening`);
289
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
290
- });
291
- this.webSocketServer.on('error', (ws, error) => {
292
- this.log.error(`WebSocketServer error: ${error}`);
293
- });
294
379
  cliEmitter.removeAllListeners();
295
380
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
296
381
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -303,25 +388,13 @@ export class Frontend extends EventEmitter {
303
388
  });
304
389
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
305
390
  const { password } = req.body;
306
- this.log.debug('The frontend sent /api/login', password);
307
- if (!this.matterbridge.nodeContext) {
308
- this.log.error('/api/login nodeContext not found');
309
- res.json({ valid: false });
310
- return;
391
+ this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
392
+ if (this.storedPassword === '' || password === this.storedPassword) {
393
+ this.log.debug('/api/login password valid');
394
+ res.json({ valid: true });
311
395
  }
312
- try {
313
- const storedPassword = await this.matterbridge.nodeContext.get('password', '');
314
- if (storedPassword === '' || password === storedPassword) {
315
- this.log.debug('/api/login password valid');
316
- res.json({ valid: true });
317
- }
318
- else {
319
- this.log.warn('/api/login error wrong password');
320
- res.json({ valid: false });
321
- }
322
- }
323
- catch (error) {
324
- this.log.error('/api/login error getting password');
396
+ else {
397
+ this.log.warn('/api/login error wrong password');
325
398
  res.json({ valid: false });
326
399
  }
327
400
  });
@@ -1480,6 +1553,7 @@ export class Frontend extends EventEmitter {
1480
1553
  case 'setpassword':
1481
1554
  if (isValidString(data.params.value)) {
1482
1555
  await this.matterbridge.nodeContext?.set('password', data.params.value);
1556
+ this.storedPassword = data.params.value;
1483
1557
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1484
1558
  }
1485
1559
  break;
@@ -146,6 +146,9 @@ export class Matterbridge extends EventEmitter {
146
146
  if (this.server.isWorkerResponse(msg, msg.type)) {
147
147
  this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
148
148
  switch (msg.type) {
149
+ case 'get_log_level':
150
+ case 'set_log_level':
151
+ break;
149
152
  default:
150
153
  this.log.debug(`Unknown broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
151
154
  }
@@ -2,6 +2,7 @@ import EventEmitter from 'node:events';
2
2
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr, debugStringify, CYAN } from 'node-ansi-logger';
3
3
  import { plg, typ } from './matterbridgeTypes.js';
4
4
  import { inspectError, logError } from './utils/error.js';
5
+ import { hasParameter } from './utils/commandLine.js';
5
6
  import { BroadcastServer } from './broadcastServer.js';
6
7
  export class PluginManager extends EventEmitter {
7
8
  _plugins = new Map();
@@ -11,7 +12,7 @@ export class PluginManager extends EventEmitter {
11
12
  constructor(matterbridge) {
12
13
  super();
13
14
  this.matterbridge = matterbridge;
14
- this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: matterbridge.log.logLevel });
15
+ this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
15
16
  this.log.debug('Matterbridge plugin manager starting...');
16
17
  this.server = new BroadcastServer('plugins', this.log);
17
18
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -24,6 +25,13 @@ export class PluginManager extends EventEmitter {
24
25
  if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'plugins')) {
25
26
  this.log.debug(`**Received request message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
26
27
  switch (msg.type) {
28
+ case 'get_log_level':
29
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
30
+ break;
31
+ case 'set_log_level':
32
+ this.log.logLevel = msg.params.logLevel;
33
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
34
+ break;
27
35
  case 'plugins_length':
28
36
  this.server.respond({ ...msg, response: { length: this.length } });
29
37
  break;