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 +4 -0
- package/dist/deviceManager.js +7 -0
- package/dist/frontend.js +136 -62
- package/dist/matterbridge.js +3 -0
- package/dist/pluginManager.js +9 -1
- package/frontend/build/assets/index.js +4 -4
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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">
|
package/dist/deviceManager.js
CHANGED
|
@@ -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(
|
|
307
|
-
if (
|
|
308
|
-
this.log.
|
|
309
|
-
res.json({ valid:
|
|
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
|
-
|
|
313
|
-
|
|
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;
|
package/dist/matterbridge.js
CHANGED
|
@@ -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
|
}
|
package/dist/pluginManager.js
CHANGED
|
@@ -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:
|
|
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;
|