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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,393 @@
1
+ import { Logger, LogLevel as MatterLogLevel } from '@matter/general';
2
+ import { BroadcastServer } from '@matterbridge/thread/server';
3
+ import { hasAnyParameter } from '@matterbridge/utils/cli';
4
+ import { writeDiagnostic } from '@matterbridge/utils/diagnostic';
5
+ import { inspectError } from '@matterbridge/utils/error';
6
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
7
+ import { isValidNumber, isValidString } from '@matterbridge/utils/validate';
8
+ import { fireAndForget, withTimeout } from '@matterbridge/utils/wait';
9
+ import { AnsiLogger, CYAN, debugStringify, nf } from 'node-ansi-logger';
10
+ import { WebSocket, WebSocketServer } from 'ws';
11
+ logModuleLoaded('BackendWsServer');
12
+ export class BackendWsServer {
13
+ debug;
14
+ verbose;
15
+ diagnostic;
16
+ log;
17
+ backend;
18
+ matterbridge;
19
+ server;
20
+ webSocketServer;
21
+ constructor(matterbridge, backend) {
22
+ this.debug = hasAnyParameter('debug', 'verbose', 'debug-backend', 'verbose-backend');
23
+ this.verbose = hasAnyParameter('verbose', 'verbose-backend');
24
+ this.diagnostic = hasAnyParameter('diagnostic', 'diagnostic-backend');
25
+ this.backend = backend;
26
+ this.matterbridge = matterbridge;
27
+ this.log = new AnsiLogger({
28
+ logName: 'BackendWsServer',
29
+ logNameColor: '\x1b[38;5;97m',
30
+ logTimestampFormat: 4,
31
+ logLevel: this.debug ? "debug" : "info",
32
+ });
33
+ this.server = new BroadcastServer('frontend', this.log);
34
+ this.server.on('broadcast_message', (msg) => fireAndForget(this.broadcastMsgHandler(msg), this.log, 'Broadcast message handler'));
35
+ }
36
+ destroy() {
37
+ this.server.close();
38
+ }
39
+ async broadcastMsgHandler(msg) {
40
+ if (this.server.isWorkerRequest(msg)) {
41
+ switch (msg.type) {
42
+ case 'get_log_level':
43
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
44
+ break;
45
+ case 'set_log_level':
46
+ this.log.logLevel = msg.params.logLevel;
47
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
48
+ break;
49
+ }
50
+ }
51
+ }
52
+ async start() {
53
+ this.log.debug(`Creating WebSocketServer...`);
54
+ this.webSocketServer = new WebSocketServer({ noServer: true });
55
+ this.backend.emit('websocket_server_listening', this.backend.secure ? 'wss' : 'ws');
56
+ this.webSocketServer.on('connection', (websocket, request) => {
57
+ const clientIp = request.socket.remoteAddress;
58
+ this.log.info(`WebSocketServer client ${CYAN}${clientIp}${nf} connected to Matterbridge`);
59
+ let callbackLogLevel = "notice";
60
+ if (this.log.logLevel === "info" || Logger.level === MatterLogLevel.INFO)
61
+ callbackLogLevel = "info";
62
+ if (this.log.logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
63
+ callbackLogLevel = "debug";
64
+ AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
65
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
66
+ websocket.on('message', (data) => {
67
+ this.log.debug(`WebSocket client ${CYAN}${clientIp}${nf} sent a message`);
68
+ fireAndForget(this.wsMessageHandler(websocket, data), this.log, `Error handling message from WebSocket client ${clientIp}`);
69
+ });
70
+ websocket.on('ping', () => {
71
+ this.log.debug(`WebSocket client ${CYAN}${clientIp}${nf} ping received`);
72
+ websocket.pong();
73
+ });
74
+ websocket.on('pong', () => {
75
+ this.log.debug(`WebSocket client ${CYAN}${clientIp}${nf} pong received`);
76
+ });
77
+ websocket.on('close', (code, reason) => {
78
+ this.log.info(`WebSocket client ${CYAN}${clientIp}${nf} disconnected code ${code} reason ${reason.toString()}`);
79
+ if (this.webSocketServer?.clients.size === 0) {
80
+ AnsiLogger.setGlobalCallback(undefined);
81
+ this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
82
+ setTimeout(() => {
83
+ if (this.webSocketServer?.clients.size === 0) {
84
+ this.log.debug('All WebSocket clients disconnected. Auth clients list cleared');
85
+ this.backend.authClients.clear();
86
+ }
87
+ }, 1000).unref();
88
+ }
89
+ else {
90
+ this.log.debug(`WebSocketServer still has ${this.webSocketServer?.clients.size} connected clients`);
91
+ }
92
+ });
93
+ websocket.on('error', (error) => {
94
+ inspectError(this.log, `WebSocket client error`, error);
95
+ });
96
+ });
97
+ this.webSocketServer.on('close', () => {
98
+ this.log.debug(`WebSocketServer closed`);
99
+ });
100
+ this.webSocketServer.on('error', (ws, error) => {
101
+ inspectError(this.log, `WebSocketServer error`, error);
102
+ });
103
+ return this.webSocketServer;
104
+ }
105
+ async stop() {
106
+ if (this.webSocketServer) {
107
+ this.log.debug('Closing WebSocket server...');
108
+ this.webSocketServer.clients.forEach((client) => {
109
+ if (client.readyState === WebSocket.OPEN) {
110
+ client.close();
111
+ }
112
+ });
113
+ await withTimeout(new Promise((resolve) => {
114
+ this.webSocketServer?.close((error) => {
115
+ if (error) {
116
+ inspectError(this.log, `Error closing WebSocket server`, error);
117
+ }
118
+ else {
119
+ this.log.debug('WebSocket server closed successfully');
120
+ this.backend.emit('websocket_server_stopped');
121
+ }
122
+ resolve();
123
+ });
124
+ }), 10000, false);
125
+ this.webSocketServer.removeAllListeners();
126
+ this.webSocketServer = undefined;
127
+ }
128
+ else {
129
+ this.log.debug('WebSocket server is not running');
130
+ }
131
+ return this.webSocketServer;
132
+ }
133
+ hasActiveClients() {
134
+ return this.webSocketServer !== undefined && this.webSocketServer.clients.size > 0;
135
+ }
136
+ async wsMessageHandler(client, rawData) {
137
+ let data;
138
+ const sendResponse = (data) => {
139
+ if (client.readyState === client.OPEN) {
140
+ if ('response' in data) {
141
+ const { response, ...rest } = data;
142
+ this.log.debug(`Sending api response message: ${debugStringify(rest)}`);
143
+ }
144
+ else {
145
+ this.log.debug(`Sending api error message: ${debugStringify(data)}`);
146
+ }
147
+ const bigintReplacer = (_, v) => (typeof v === 'bigint' ? `${v}n` : v);
148
+ client.send(JSON.stringify(data, bigintReplacer));
149
+ }
150
+ else {
151
+ this.log.error('Cannot send api response, client not connected');
152
+ }
153
+ };
154
+ try {
155
+ const raw = Array.isArray(rawData) ? Buffer.concat(rawData) : rawData instanceof ArrayBuffer ? Buffer.from(rawData) : rawData;
156
+ data = JSON.parse(raw.toString());
157
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
158
+ this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
159
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
160
+ return;
161
+ }
162
+ this.log.debug(`Received message from websocket client: ${debugStringify(data)}`);
163
+ if (data.method === 'ping') {
164
+ sendResponse({ id: data.id, method: 'pong', src: 'Matterbridge', dst: data.src, success: true, response: 'pong' });
165
+ }
166
+ else if (data.method === '/api/restart') {
167
+ this.wssSendSnackbarMessage('Restarting matterbridge...', 0);
168
+ await this.server.fetch({ type: 'matterbridge_restart', src: 'frontend', dst: 'matterbridge', params: undefined });
169
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
170
+ }
171
+ else if (data.method === '/api/shutdown') {
172
+ this.wssSendSnackbarMessage('Shutting down matterbridge...', 0);
173
+ await this.server.fetch({ type: 'matterbridge_shutdown', src: 'frontend', dst: 'matterbridge', params: undefined });
174
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
175
+ }
176
+ else if (data.method === '/api/settings') {
177
+ const start = performance.now();
178
+ const settings = await this.backend.getApiSettings();
179
+ if (this.diagnostic)
180
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiSettings() took ${(performance.now() - start).toFixed(2)} ms`);
181
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: settings });
182
+ }
183
+ else if (data.method === '/api/plugins') {
184
+ const start = performance.now();
185
+ const plugins = await this.backend.getApiPlugins();
186
+ if (this.diagnostic)
187
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiPlugins() took ${(performance.now() - start).toFixed(2)} ms`);
188
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
189
+ }
190
+ else if (data.method === '/api/devices') {
191
+ const start = performance.now();
192
+ const devices = await this.backend.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
193
+ if (this.diagnostic)
194
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiDevices() took ${(performance.now() - start).toFixed(2)} ms`);
195
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
196
+ }
197
+ else if (data.method === '/api/clusters') {
198
+ if (!isValidString(data.params.plugin, 10)) {
199
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter plugin in /api/clusters' });
200
+ return;
201
+ }
202
+ if (!isValidNumber(data.params.endpoint, 1)) {
203
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter endpoint in /api/clusters' });
204
+ return;
205
+ }
206
+ const start = performance.now();
207
+ const clusters = await this.backend.getApiCluster(data.params.plugin, data.params.endpoint, data.params.serialNumber, data.params.uniqueId);
208
+ if (this.diagnostic)
209
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiCluster() took ${(performance.now() - start).toFixed(2)} ms`);
210
+ if (clusters) {
211
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: clusters });
212
+ }
213
+ else {
214
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Endpoint not found in /api/clusters' });
215
+ }
216
+ }
217
+ else if (data.method === '/api/matter') {
218
+ if (!isValidString(data.params.id)) {
219
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter id in /api/matter' });
220
+ return;
221
+ }
222
+ const matter = await this.backend.getApiMatter(data.params.id);
223
+ if (!matter) {
224
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Unknown server node id ${data.params.id} in /api/matter` });
225
+ return;
226
+ }
227
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: matter });
228
+ this.wssSendRefreshRequired('matter', { matter });
229
+ }
230
+ }
231
+ catch (error) {
232
+ inspectError(this.log, `Error parsing message from websocket client`, error);
233
+ return;
234
+ }
235
+ }
236
+ wssBroadcastMessage(msg) {
237
+ if (!this.hasActiveClients())
238
+ return;
239
+ try {
240
+ const stringifiedMsg = JSON.stringify(msg);
241
+ this.webSocketServer?.clients.forEach((client) => {
242
+ if (client.readyState === client.OPEN) {
243
+ client.send(stringifiedMsg);
244
+ }
245
+ });
246
+ }
247
+ catch (error) {
248
+ inspectError(this.log, `Error broadcasting message to websocket clients`, error);
249
+ }
250
+ }
251
+ wssSendLogMessage(level, time, name, message) {
252
+ if (!this.hasActiveClients())
253
+ return;
254
+ if (!level || !time || !name || !message)
255
+ return;
256
+ message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
257
+ message = message.replace(/^\*+/, '');
258
+ message = message.replace(/[\t\n]/g, '');
259
+ message = message.replace(/[\x00-\x1F\x7F]/g, '');
260
+ message = message.replace(/\\"/g, '"');
261
+ const maxContinuousLength = 100;
262
+ const keepStartLength = 20;
263
+ const keepEndLength = 20;
264
+ if (level !== 'spawn') {
265
+ message = message
266
+ .split(' ')
267
+ .map((word) => {
268
+ if (word.length > maxContinuousLength) {
269
+ return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
270
+ }
271
+ return word;
272
+ })
273
+ .join(' ');
274
+ }
275
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
276
+ }
277
+ wssSendRefreshRequired(changed, params) {
278
+ if (!this.hasActiveClients())
279
+ return;
280
+ if (this.verbose)
281
+ this.log.debug('Sending a refresh required message to all connected clients');
282
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, lock: params?.lock, ...params } });
283
+ }
284
+ wssSendRestartRequired(snackbar = true, fixed = false) {
285
+ if (!this.hasActiveClients())
286
+ return;
287
+ if (this.verbose)
288
+ this.log.debug('Sending a restart required message to all connected clients');
289
+ if (snackbar)
290
+ this.wssSendSnackbarMessage(`Restart required`, 0);
291
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
292
+ }
293
+ wssSendRestartNotRequired(snackbar = true) {
294
+ if (!this.hasActiveClients())
295
+ return;
296
+ if (this.verbose)
297
+ this.log.debug('Sending a restart not required message to all connected clients');
298
+ if (snackbar)
299
+ this.wssSendCloseSnackbarMessage(`Restart required`);
300
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
301
+ }
302
+ wssSendUpdateRequired(version, devVersion = false) {
303
+ if (!this.hasActiveClients())
304
+ return;
305
+ if (this.verbose)
306
+ this.log.debug('Sending a matterbridge version update required message to all connected clients');
307
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { version, devVersion } });
308
+ }
309
+ wssSendPluginUpdateRequired(plugin, version, devVersion = false) {
310
+ if (!this.hasActiveClients())
311
+ return;
312
+ if (this.verbose)
313
+ this.log.debug('Sending a plugin version update required message to all connected clients');
314
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_update_required', success: true, response: { plugin, version, devVersion } });
315
+ }
316
+ wssSendPluginStatusUpdate(plugin, status) {
317
+ if (!this.hasActiveClients())
318
+ return;
319
+ if (this.verbose)
320
+ this.log.debug('Sending a plugin status update message to all connected clients');
321
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_status_update', success: true, response: { plugin, status } });
322
+ }
323
+ wssSendMatterbridgeStatusUpdate(status) {
324
+ if (!this.hasActiveClients())
325
+ return;
326
+ if (this.verbose)
327
+ this.log.debug('Sending a matterbridge status update message to all connected clients');
328
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'matterbridge_status_update', success: true, response: { status } });
329
+ }
330
+ wssSendCpuUpdate(cpuUsage, processCpuUsage) {
331
+ if (!this.hasActiveClients())
332
+ return;
333
+ if (this.verbose)
334
+ this.log.debug('Sending a cpu update message to all connected clients');
335
+ this.wssBroadcastMessage({
336
+ id: 0,
337
+ src: 'Matterbridge',
338
+ dst: 'Frontend',
339
+ method: 'cpu_update',
340
+ success: true,
341
+ response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 },
342
+ });
343
+ }
344
+ wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
345
+ if (!this.hasActiveClients())
346
+ return;
347
+ if (this.verbose)
348
+ this.log.debug('Sending a memory update message to all connected clients');
349
+ this.wssBroadcastMessage({
350
+ id: 0,
351
+ src: 'Matterbridge',
352
+ dst: 'Frontend',
353
+ method: 'memory_update',
354
+ success: true,
355
+ response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers },
356
+ });
357
+ }
358
+ wssSendUptimeUpdate(systemUptime, processUptime) {
359
+ if (!this.hasActiveClients())
360
+ return;
361
+ if (this.verbose)
362
+ this.log.debug('Sending a uptime update message to all connected clients');
363
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
364
+ }
365
+ wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
366
+ if (!this.hasActiveClients())
367
+ return;
368
+ if (this.verbose)
369
+ this.log.debug('Sending a snackbar message to all connected clients');
370
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
371
+ }
372
+ wssSendCloseSnackbarMessage(message) {
373
+ if (!this.hasActiveClients())
374
+ return;
375
+ if (this.verbose)
376
+ this.log.debug('Sending a close snackbar message to all connected clients');
377
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
378
+ }
379
+ wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
380
+ if (!this.hasActiveClients())
381
+ return;
382
+ if (this.verbose)
383
+ this.log.debug('Sending an attribute update message to all connected clients');
384
+ this.wssBroadcastMessage({
385
+ id: 0,
386
+ src: 'Matterbridge',
387
+ dst: 'Frontend',
388
+ method: 'state_update',
389
+ success: true,
390
+ response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value },
391
+ });
392
+ }
393
+ }
package/dist/cli.js CHANGED
@@ -1,3 +1,5 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
1
3
  import { ThreadsManager } from '@matterbridge/thread/manager';
2
4
  import { hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
3
5
  import { inspectError } from '@matterbridge/utils/error';
@@ -11,7 +13,7 @@ logModuleLoaded('Cli');
11
13
  export let instance;
12
14
  export const tracker = new Tracker('Cli', false, false);
13
15
  export const inspector = new Inspector('Cli', false, false);
14
- const manager = new ThreadsManager();
16
+ const manager = new ThreadsManager(path.dirname(fileURLToPath(import.meta.url)));
15
17
  const colorEnabled = !process.env.NO_COLOR && process.env.TERM !== 'dumb' && process.env.FORCE_COLOR !== '0' && !hasParameter('no-ansi');
16
18
  if (!colorEnabled)
17
19
  process.env.NO_COLOR = '1';
@@ -100,10 +102,10 @@ async function main() {
100
102
  const { Matterbridge } = await import('./matterbridge.js');
101
103
  instance = await Matterbridge.loadInstance(true);
102
104
  log.debug(`***Matterbridge.loadInstance(true) exited`);
103
- if (hasParameter('experimental')) {
104
- log.debug(`***Starting Backend thread...`);
105
+ if (hasParameter('experimental-backend')) {
106
+ log.debug(`***Starting experimental Backend thread...`);
105
107
  manager.runThread('Backend');
106
- log.debug(`***Started Backend thread`);
108
+ log.debug(`***Started experimental Backend thread`);
107
109
  }
108
110
  if (!instance || instance.shutdown) {
109
111
  await shutdown();
@@ -69,14 +69,15 @@ export class MatterNode extends EventEmitter {
69
69
  this.log.logNameColor = '\x1b[38;5;65m';
70
70
  if (this.debug)
71
71
  this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loading...`);
72
+ this.storeId = this.device?.deviceName ? this.device.deviceName.replace(/[ .]/g, '') : (this.pluginName ?? 'Matterbridge');
72
73
  this.port = matterbridge.port;
73
74
  this.passcode = matterbridge.passcode;
74
75
  this.discriminator = matterbridge.discriminator;
76
+ this.mdnsInterface = matterbridge.mdnsInterface;
75
77
  this.server = new BroadcastServer('matter', this.log);
76
78
  this.server.on('broadcast_message', this.msgHandler.bind(this));
77
79
  if (this.verbose)
78
80
  this.log.debug(`BroadcastServer is ready`);
79
- fs.mkdirSync(matterbridge.matterbridgeDirectory, { recursive: true });
80
81
  this.pluginManager = new PluginManager(this.matterbridge);
81
82
  this.pluginManager.logLevel = this.debug ? "debug" : "info";
82
83
  this.pluginManager.server.close();
@@ -87,6 +88,8 @@ export class MatterNode extends EventEmitter {
87
88
  this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR));
88
89
  this.environment.vars.set('runtime.signals', false);
89
90
  this.environment.vars.set('runtime.exitcode', false);
91
+ if (this.mdnsInterface)
92
+ this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
90
93
  if (this.verbose)
91
94
  this.log.debug(`Matter Environment is ready`);
92
95
  this.matterMdnsService = new MdnsService(this.environment);
@@ -117,6 +120,34 @@ export class MatterNode extends EventEmitter {
117
120
  this.log.logLevel = msg.params.logLevel;
118
121
  this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
119
122
  break;
123
+ case 'matter_start':
124
+ if (this.device || msg.params.storeId !== this.storeId)
125
+ break;
126
+ try {
127
+ await this.create();
128
+ await this.start();
129
+ this.server.respond({ ...msg, result: { storeId: this.storeId, success: true } });
130
+ }
131
+ catch (error) {
132
+ inspectError(this.log, `Error starting MatterNode ${this.storeId}`, error);
133
+ }
134
+ break;
135
+ case 'matter_stop':
136
+ if (this.device || msg.params.storeId !== this.storeId)
137
+ break;
138
+ try {
139
+ await this.stop();
140
+ this.server.respond({ ...msg, result: { storeId: this.storeId, success: true } });
141
+ await this.destroy(!this.pluginName);
142
+ }
143
+ catch (error) {
144
+ inspectError(this.log, `Error stopping MatterNode ${this.storeId}`, error);
145
+ }
146
+ break;
147
+ case 'matter_apimatter':
148
+ if (this.serverNode?.id === msg.params.id)
149
+ this.server.respond({ ...msg, result: { matter: getServerNodeData(this.serverNode) } });
150
+ break;
120
151
  default:
121
152
  if (this.verbose)
122
153
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
@@ -153,6 +184,10 @@ export class MatterNode extends EventEmitter {
153
184
  }
154
185
  async create() {
155
186
  this.log.info('Creating Matter node...');
187
+ const { result } = await this.server.fetch({ type: 'matterbridge_matterdata', src: this.server.name, dst: 'matterbridge' });
188
+ this.port = result.port;
189
+ this.passcode = result.passcode;
190
+ this.discriminator = result.discriminator;
156
191
  await this.startMatterStorage();
157
192
  this.pluginManager.matterbridge.nodeStorage = new NodeStorageManager({
158
193
  dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR),
@@ -967,7 +1002,7 @@ export class MatterNode extends EventEmitter {
967
1002
  child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
968
1003
  }
969
1004
  }
970
- async yieldToNode(timeout = 100) {
1005
+ async yieldToNode(timeout = 10) {
971
1006
  await Promise.resolve();
972
1007
  await new Promise((resolve) => {
973
1008
  setImmediate(resolve);
@@ -170,6 +170,13 @@ export class Matterbridge extends EventEmitter {
170
170
  this.server.close();
171
171
  }
172
172
  getPlatformMatterbridge() {
173
+ this.systemInformation.totalMemory = formatBytes(os.totalmem());
174
+ this.systemInformation.freeMemory = formatBytes(os.freemem());
175
+ this.systemInformation.systemUptime = formatUptime(os.uptime());
176
+ this.systemInformation.processUptime = formatUptime(Math.floor(process.uptime()));
177
+ this.systemInformation.rss = formatBytes(memoryFootprint());
178
+ this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
179
+ this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
173
180
  return {
174
181
  systemInformation: { ...this.systemInformation },
175
182
  uuid: this.uuid,
@@ -193,6 +200,13 @@ export class Matterbridge extends EventEmitter {
193
200
  };
194
201
  }
195
202
  getSharedMatterbridge() {
203
+ this.systemInformation.totalMemory = formatBytes(os.totalmem());
204
+ this.systemInformation.freeMemory = formatBytes(os.freemem());
205
+ this.systemInformation.systemUptime = formatUptime(os.uptime());
206
+ this.systemInformation.processUptime = formatUptime(Math.floor(process.uptime()));
207
+ this.systemInformation.rss = formatBytes(memoryFootprint());
208
+ this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
209
+ this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
196
210
  return {
197
211
  systemInformation: { ...this.systemInformation },
198
212
  uuid: this.uuid,
@@ -318,6 +332,15 @@ export class Matterbridge extends EventEmitter {
318
332
  await this.nodeContext?.set('globalModulesDirectory', msg.params.prefix);
319
333
  this.server.respond({ ...msg, result: { success: true } });
320
334
  break;
335
+ case 'matterbridge_matterdata':
336
+ this.server.respond({ ...msg, result: { port: this.port, passcode: this.passcode, discriminator: this.discriminator } });
337
+ if (this.port !== undefined)
338
+ this.port++;
339
+ if (this.passcode !== undefined)
340
+ this.passcode++;
341
+ if (this.discriminator !== undefined)
342
+ this.discriminator++;
343
+ break;
321
344
  case 'matterbridge_platform':
322
345
  this.server.respond({ ...msg, result: { data: this.getPlatformMatterbridge(), success: true } });
323
346
  break;
@@ -750,6 +773,8 @@ export class Matterbridge extends EventEmitter {
750
773
  }
751
774
  else {
752
775
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
776
+ if (this.globalModulesDirectory.includes('.bun'))
777
+ this.globalModulesDirectory = '';
753
778
  }
754
779
  if (this.globalModulesDirectory === '') {
755
780
  this.log.debug(`Getting global node_modules directory...`);
@@ -924,7 +949,7 @@ export class Matterbridge extends EventEmitter {
924
949
  this.shutdown = true;
925
950
  return;
926
951
  }
927
- if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
952
+ if ((getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined) && !hasParameter('experimental-backend'))
928
953
  await this.frontend.start(getIntParameter('frontend'));
929
954
  try {
930
955
  await this.startMatterStorage();
@@ -1017,15 +1042,45 @@ export class Matterbridge extends EventEmitter {
1017
1042
  this.log.warn('Fixed delay switch found. Waiting for ' + delay + ' seconds before starting matterbridge...');
1018
1043
  await wait(delay * 1000, 'Fixed race condition delay', true);
1019
1044
  }
1045
+ if (hasParameter('experimental-tracker')) {
1046
+ this.log.notice('***Experimental tracker enabled for Threads');
1047
+ for (let i = 0; i < 10; i++) {
1048
+ this.server.request({ type: 'manager_run', src: 'matterbridge', dst: 'manager', params: { name: 'Tracker' } });
1049
+ }
1050
+ }
1020
1051
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
1021
1052
  this.bridgeMode = 'bridge';
1022
1053
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
1054
+ if (hasParameter('experimental-node')) {
1055
+ this.log.notice(`***Starting experimental MatterNode in ${this.bridgeMode} mode...`);
1056
+ this.server.request({
1057
+ type: 'manager_run',
1058
+ src: 'matterbridge',
1059
+ dst: 'manager',
1060
+ params: { name: 'RootNode', workerData: { threadName: 'RootNode', sharedMatterbridge: this.getSharedMatterbridge() } },
1061
+ });
1062
+ this.log.notice(`***Started experimental MatterNode in ${this.bridgeMode} mode`);
1063
+ return;
1064
+ }
1023
1065
  await this.startBridge();
1024
1066
  return;
1025
1067
  }
1026
1068
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
1027
1069
  this.bridgeMode = 'childbridge';
1028
1070
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
1071
+ if (hasParameter('experimental-node')) {
1072
+ this.log.notice(`***Starting experimental MatterNode in ${this.bridgeMode} mode...`);
1073
+ for (const plugin of this.plugins.array().filter((p) => p.enabled)) {
1074
+ this.server.request({
1075
+ type: 'manager_run',
1076
+ src: 'matterbridge',
1077
+ dst: 'manager',
1078
+ params: { name: 'RootNode', workerData: { threadName: 'RootNode', sharedMatterbridge: this.getSharedMatterbridge(), pluginName: plugin.name } },
1079
+ });
1080
+ }
1081
+ this.log.notice(`***Started experimental MatterNode in ${this.bridgeMode} mode`);
1082
+ return;
1083
+ }
1029
1084
  await this.startChildbridge();
1030
1085
  return;
1031
1086
  }
@@ -1478,6 +1533,8 @@ export class Matterbridge extends EventEmitter {
1478
1533
  this.frontend.destroy();
1479
1534
  this.plugins.destroy();
1480
1535
  this.devices.destroy();
1536
+ this.server.request({ type: 'manager_shutdown', src: 'matterbridge', dst: 'manager', params: {} });
1537
+ await wait(100);
1481
1538
  this.server.close();
1482
1539
  if (this.nodeStorage && this.nodeContext) {
1483
1540
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
@@ -0,0 +1,3 @@
1
+ import { ThreadsWrapper } from '@matterbridge/thread/wrapper';
2
+ declare const _default: ThreadsWrapper;
3
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import { ThreadsWrapper } from '@matterbridge/thread/wrapper';
2
+ import { inspectError } from '@matterbridge/utils/error';
3
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
4
+ logModuleLoaded('Backend', '\u001B[35m');
5
+ export default new ThreadsWrapper('Backend', async (worker) => {
6
+ worker.logger("info", 'Creating backend...');
7
+ try {
8
+ worker.logger("debug", 'Fetching shared Matterbridge state...');
9
+ const shared = (await worker.server.fetch({ type: 'matterbridge_shared', src: 'matterbridge', dst: 'matterbridge' }, 1000)).result.data;
10
+ worker.logger("debug", 'Shared Matterbridge state fetched; importing Backend...');
11
+ const { Backend } = await import('../backend.js');
12
+ worker.logger("debug", 'Backend imported; creating instance...');
13
+ const backend = new Backend(shared);
14
+ worker.logger("debug", 'Starting Backend...');
15
+ await backend.start();
16
+ worker.logger("debug", 'Backend started');
17
+ return true;
18
+ }
19
+ catch (error) {
20
+ const errorMessage = inspectError(worker.log, 'Failed to create backend', error);
21
+ worker.logger("error", errorMessage);
22
+ return false;
23
+ }
24
+ });
@@ -0,0 +1,3 @@
1
+ import { ThreadsWrapper } from '@matterbridge/thread/wrapper';
2
+ declare const _default: ThreadsWrapper;
3
+ export default _default;