@matterbridge/core 3.8.0-dev-20260528-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.
- package/dist/backend.d.ts +37 -0
- package/dist/backend.js +294 -0
- package/dist/backendExpress.d.ts +3 -3
- package/dist/backendExpress.js +11 -11
- package/dist/backendWsServer.d.ts +4 -4
- package/dist/backendWsServer.js +6 -9
- package/dist/behaviors/activatedCarbonFilterMonitoringServer.d.ts +1 -1
- package/dist/behaviors/hepaFilterMonitoringServer.d.ts +1 -1
- package/dist/matterNode.js +3 -2
- package/package.json +5 -5
|
@@ -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 {};
|
package/dist/backend.js
ADDED
|
@@ -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
|
+
}
|
package/dist/backendExpress.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { type SharedMatterbridge } from '@matterbridge/types';
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import
|
|
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
|
-
|
|
12
|
+
expressApp: express.Application | undefined;
|
|
13
|
+
constructor(matterbridge: SharedMatterbridge, backend: Backend);
|
|
14
14
|
destroy(): void;
|
|
15
15
|
private broadcastMsgHandler;
|
|
16
16
|
private validateReq;
|
package/dist/backendExpress.js
CHANGED
|
@@ -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
|
|
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
|
|
5
|
-
export declare class
|
|
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
|
-
|
|
12
|
+
webSocketServer: WebSocketServer | undefined;
|
|
13
|
+
constructor(matterbridge: SharedMatterbridge, backend: Backend);
|
|
14
14
|
destroy(): void;
|
|
15
15
|
private broadcastMsgHandler;
|
|
16
16
|
start(): Promise<WebSocketServer | undefined>;
|
package/dist/backendWsServer.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|
package/dist/matterNode.js
CHANGED
|
@@ -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
|
|
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
|
|
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-20260528-
|
|
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-20260528-
|
|
134
|
-
"@matterbridge/thread": "3.8.0-dev-20260528-
|
|
135
|
-
"@matterbridge/types": "3.8.0-dev-20260528-
|
|
136
|
-
"@matterbridge/utils": "3.8.0-dev-20260528-
|
|
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",
|