matterbridge 2.1.3-dev.1 → 2.1.3

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/cli.d.ts +25 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +26 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +114 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +94 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/frontend.d.ts +110 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +231 -23
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +5 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +409 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +748 -40
  57. package/dist/matterbridge.js.map +1 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  60. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  61. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  62. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  63. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  64. package/dist/matterbridgeBehaviors.js +32 -1
  65. package/dist/matterbridgeBehaviors.js.map +1 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeDeviceTypes.js +112 -11
  69. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  72. package/dist/matterbridgeDynamicPlatform.js +33 -0
  73. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  74. package/dist/matterbridgeEndpoint.d.ts +834 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +690 -6
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2262 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +96 -0
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +152 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +111 -3
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +167 -0
  87. package/dist/matterbridgeTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeTypes.js +24 -0
  89. package/dist/matterbridgeTypes.js.map +1 -0
  90. package/dist/pluginManager.d.ts +236 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +230 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/storage/export.d.ts +2 -0
  95. package/dist/storage/export.d.ts.map +1 -0
  96. package/dist/storage/export.js +1 -0
  97. package/dist/storage/export.js.map +1 -0
  98. package/dist/utils/colorUtils.d.ts +61 -0
  99. package/dist/utils/colorUtils.d.ts.map +1 -0
  100. package/dist/utils/colorUtils.js +205 -2
  101. package/dist/utils/colorUtils.js.map +1 -0
  102. package/dist/utils/export.d.ts +3 -0
  103. package/dist/utils/export.d.ts.map +1 -0
  104. package/dist/utils/export.js +1 -0
  105. package/dist/utils/export.js.map +1 -0
  106. package/dist/utils/utils.d.ts +221 -0
  107. package/dist/utils/utils.d.ts.map +1 -0
  108. package/dist/utils/utils.js +251 -7
  109. package/dist/utils/utils.js.map +1 -0
  110. package/npm-shrinkwrap.json +2 -2
  111. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,4 +1,28 @@
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @date 2025-01-13
7
+ * @version 1.0.1
8
+ *
9
+ * Copyright 2025, 2026, 2027 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // @matter
1
24
  import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat } from '@matter/main';
25
+ // Node modules
2
26
  import { createServer } from 'http';
3
27
  import https from 'https';
4
28
  import express from 'express';
@@ -6,13 +30,32 @@ import WebSocket, { WebSocketServer } from 'ws';
6
30
  import os from 'os';
7
31
  import path from 'path';
8
32
  import { promises as fs } from 'fs';
33
+ // AnsiLogger module
9
34
  import { AnsiLogger, CYAN, db, debugStringify, er, nf, rs, stringify, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
35
+ // Matterbridge
10
36
  import { createZip, getIntParameter, hasParameter, isValidNumber, isValidObject, isValidString } from './utils/utils.js';
11
37
  import { plg } from './matterbridgeTypes.js';
12
38
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
39
+ /**
40
+ * Websocket message ID for logging.
41
+ * @constant {number}
42
+ */
13
43
  export const WS_ID_LOG = 0;
44
+ /**
45
+ * Websocket message ID indicating a refresh is needed.
46
+ * @constant {number}
47
+ */
14
48
  export const WS_ID_REFRESH_NEEDED = 1;
49
+ /**
50
+ * Websocket message ID indicating a restart is needed.
51
+ * @constant {number}
52
+ */
15
53
  export const WS_ID_RESTART_NEEDED = 2;
54
+ /**
55
+ * Initializes the frontend of Matterbridge.
56
+ *
57
+ * @param port The port number to run the frontend server on. Default is 8283.
58
+ */
16
59
  export class Frontend {
17
60
  matterbridge;
18
61
  log;
@@ -28,7 +71,7 @@ export class Frontend {
28
71
  memoryTimeout;
29
72
  constructor(matterbridge) {
30
73
  this.matterbridge = matterbridge;
31
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
74
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
32
75
  }
33
76
  set logLevel(logLevel) {
34
77
  this.log.logLevel = logLevel;
@@ -36,10 +79,21 @@ export class Frontend {
36
79
  async start(port = 8283) {
37
80
  this.port = port;
38
81
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
82
+ // Create the express app that serves the frontend
39
83
  this.expressApp = express();
84
+ // Log all requests to the server for debugging
85
+ /*
86
+ this.expressApp.use((req, res, next) => {
87
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
88
+ next();
89
+ });
90
+ */
91
+ // Serve static files from '/static' endpoint
40
92
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
41
93
  if (!hasParameter('ssl')) {
94
+ // Create an HTTP server and attach the express app
42
95
  this.httpServer = createServer(this.expressApp);
96
+ // Listen on the specified port
43
97
  if (hasParameter('ingress')) {
44
98
  this.httpServer.listen(this.port, '0.0.0.0', () => {
45
99
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -53,6 +107,7 @@ export class Frontend {
53
107
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
54
108
  });
55
109
  }
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
111
  this.httpServer.on('error', (error) => {
57
112
  this.log.error(`Frontend http server error listening on ${this.port}`);
58
113
  switch (error.code) {
@@ -68,6 +123,7 @@ export class Frontend {
68
123
  });
69
124
  }
70
125
  else {
126
+ // Load the SSL certificate, the private key and optionally the CA certificate
71
127
  let cert;
72
128
  try {
73
129
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -95,7 +151,9 @@ export class Frontend {
95
151
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
96
152
  }
97
153
  const serverOptions = { cert, key, ca };
154
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
98
155
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
156
+ // Listen on the specified port
99
157
  if (hasParameter('ingress')) {
100
158
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
101
159
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -109,6 +167,7 @@ export class Frontend {
109
167
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
110
168
  });
111
169
  }
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
171
  this.httpsServer.on('error', (error) => {
113
172
  this.log.error(`Frontend https server error listening on ${this.port}`);
114
173
  switch (error.code) {
@@ -125,12 +184,13 @@ export class Frontend {
125
184
  }
126
185
  if (this.initializeError)
127
186
  return;
187
+ // Createe a WebSocket server and attach it to the http or https server
128
188
  const wssPort = this.port;
129
189
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
130
190
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
131
191
  this.webSocketServer.on('connection', (ws, request) => {
132
192
  const clientIp = request.socket.remoteAddress;
133
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
193
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
134
194
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
135
195
  ws.on('message', (message) => {
136
196
  this.wsMessageHandler(ws, message);
@@ -162,9 +222,11 @@ export class Frontend {
162
222
  this.webSocketServer.on('error', (ws, error) => {
163
223
  this.log.error(`WebSocketServer error: ${error}`);
164
224
  });
225
+ // Start the memory dump interval
165
226
  if (hasParameter('memorydump')) {
166
227
  this.startCpuMemoryDump();
167
228
  }
229
+ // Endpoint to validate login code
168
230
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
169
231
  const { password } = req.body;
170
232
  this.log.debug('The frontend sent /api/login', password);
@@ -183,23 +245,27 @@ export class Frontend {
183
245
  this.log.warn('/api/login error wrong password');
184
246
  res.json({ valid: false });
185
247
  }
248
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
186
249
  }
187
250
  catch (error) {
188
251
  this.log.error('/api/login error getting password');
189
252
  res.json({ valid: false });
190
253
  }
191
254
  });
255
+ // Endpoint to provide health check
192
256
  this.expressApp.get('/health', (req, res) => {
193
257
  this.log.debug('Express received /health');
194
258
  const healthStatus = {
195
- status: 'ok',
196
- uptime: process.uptime(),
197
- timestamp: new Date().toISOString(),
259
+ status: 'ok', // Indicate service is healthy
260
+ uptime: process.uptime(), // Server uptime in seconds
261
+ timestamp: new Date().toISOString(), // Current timestamp
198
262
  };
199
263
  res.status(200).json(healthStatus);
200
264
  });
265
+ // Endpoint to provide memory usage details
201
266
  this.expressApp.get('/memory', async (req, res) => {
202
267
  this.log.debug('Express received /memory');
268
+ // Memory usage from process
203
269
  const memoryUsageRaw = process.memoryUsage();
204
270
  const memoryUsage = {
205
271
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -208,10 +274,13 @@ export class Frontend {
208
274
  external: this.formatMemoryUsage(memoryUsageRaw.external),
209
275
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
210
276
  };
277
+ // V8 heap statistics
211
278
  const { default: v8 } = await import('node:v8');
212
279
  const heapStatsRaw = v8.getHeapStatistics();
213
280
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
281
+ // Format heapStats
214
282
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
283
+ // Format heapSpaces
215
284
  const heapSpaces = heapSpacesRaw.map((space) => ({
216
285
  ...space,
217
286
  space_size: this.formatMemoryUsage(space.space_size),
@@ -221,6 +290,23 @@ export class Frontend {
221
290
  }));
222
291
  const { default: module } = await import('module');
223
292
  const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
293
+ /*
294
+ if (req.query.heapdump === 'true') {
295
+ const { default: heapdump } = await import('heapdump');
296
+ const filename = `heapdump-${Date.now()}.heapsnapshot`;
297
+
298
+ heapdump.writeSnapshot(filename, (err, dumpFilename) => {
299
+ if (err) {
300
+ this.log.error(`Heap dump error: ${err.message}`);
301
+ return res.status(500).json({ error: 'Heap dump failed', details: err.message });
302
+ }
303
+
304
+ this.log.info(`Heap dump written to ${dumpFilename}`);
305
+ return res.status(200).json({ ...memoryReport, heapdump: dumpFilename });
306
+ });
307
+ return; // Exit early since heapdump response is handled inside callback
308
+ }
309
+ */
224
310
  const memoryReport = {
225
311
  memoryUsage,
226
312
  heapStats,
@@ -229,6 +315,7 @@ export class Frontend {
229
315
  };
230
316
  res.status(200).json(memoryReport);
231
317
  });
318
+ // Endpoint to start advertising the server node
232
319
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
233
320
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
234
321
  if (pairingCodes) {
@@ -239,19 +326,24 @@ export class Frontend {
239
326
  res.status(500).json({ error: 'Failed to generate pairing codes' });
240
327
  }
241
328
  });
329
+ // Endpoint to provide settings
242
330
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
243
331
  this.log.debug('The frontend sent /api/settings');
244
332
  res.json(await this.getApiSettings());
245
333
  });
334
+ // Endpoint to provide plugins
246
335
  this.expressApp.get('/api/plugins', async (req, res) => {
247
336
  this.log.debug('The frontend sent /api/plugins');
248
337
  const response = this.getBaseRegisteredPlugins();
338
+ // this.log.debug('Response:', debugStringify(response));
249
339
  res.json(response);
250
340
  });
341
+ // Endpoint to provide devices
251
342
  this.expressApp.get('/api/devices', (req, res) => {
252
343
  this.log.debug('The frontend sent /api/devices');
253
344
  const devices = [];
254
345
  this.matterbridge.devices.forEach(async (device) => {
346
+ // Check if the device has the required properties
255
347
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
256
348
  return;
257
349
  const cluster = this.getClusterTextFromDevice(device);
@@ -267,8 +359,10 @@ export class Frontend {
267
359
  cluster: cluster,
268
360
  });
269
361
  });
362
+ // this.log.debug('Response:', debugStringify(data));
270
363
  res.json(devices);
271
364
  });
365
+ // Endpoint to provide the cluster servers of the devices
272
366
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
273
367
  const selectedPluginName = req.params.selectedPluginName;
274
368
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -341,6 +435,7 @@ export class Frontend {
341
435
  });
342
436
  res.json(data);
343
437
  });
438
+ // Endpoint to view the log
344
439
  this.expressApp.get('/api/view-log', async (req, res) => {
345
440
  this.log.debug('The frontend sent /api/log');
346
441
  try {
@@ -353,10 +448,12 @@ export class Frontend {
353
448
  res.status(500).send('Error reading log file');
354
449
  }
355
450
  });
451
+ // Endpoint to download the matterbridge log
356
452
  this.expressApp.get('/api/download-mblog', async (req, res) => {
357
453
  this.log.debug('The frontend sent /api/download-mblog');
358
454
  try {
359
455
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
456
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
360
457
  }
361
458
  catch (error) {
362
459
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -368,10 +465,12 @@ export class Frontend {
368
465
  }
369
466
  });
370
467
  });
468
+ // Endpoint to download the matter log
371
469
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
372
470
  this.log.debug('The frontend sent /api/download-mjlog');
373
471
  try {
374
472
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
473
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
375
474
  }
376
475
  catch (error) {
377
476
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -383,6 +482,7 @@ export class Frontend {
383
482
  }
384
483
  });
385
484
  });
485
+ // Endpoint to download the matter storage file
386
486
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
387
487
  this.log.debug('The frontend sent /api/download-mjstorage');
388
488
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -393,6 +493,7 @@ export class Frontend {
393
493
  }
394
494
  });
395
495
  });
496
+ // Endpoint to download the matterbridge storage directory
396
497
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
397
498
  this.log.debug('The frontend sent /api/download-mbstorage');
398
499
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -403,6 +504,7 @@ export class Frontend {
403
504
  }
404
505
  });
405
506
  });
507
+ // Endpoint to download the matterbridge plugin directory
406
508
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
407
509
  this.log.debug('The frontend sent /api/download-pluginstorage');
408
510
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -413,9 +515,11 @@ export class Frontend {
413
515
  }
414
516
  });
415
517
  });
518
+ // Endpoint to download the matterbridge plugin config files
416
519
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
417
520
  this.log.debug('The frontend sent /api/download-pluginconfig');
418
521
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
522
+ // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
419
523
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
420
524
  if (error) {
421
525
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -423,6 +527,7 @@ export class Frontend {
423
527
  }
424
528
  });
425
529
  });
530
+ // Endpoint to download the matterbridge plugin config files
426
531
  this.expressApp.get('/api/download-backup', async (req, res) => {
427
532
  this.log.debug('The frontend sent /api/download-backup');
428
533
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -432,6 +537,7 @@ export class Frontend {
432
537
  }
433
538
  });
434
539
  });
540
+ // Endpoint to receive commands
435
541
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
436
542
  const command = req.params.command;
437
543
  let param = req.params.param;
@@ -441,13 +547,15 @@ export class Frontend {
441
547
  return;
442
548
  }
443
549
  this.log.debug(`Received frontend command: ${command}:${param}`);
550
+ // Handle the command setpassword from Settings
444
551
  if (command === 'setpassword') {
445
- const password = param.slice(1, -1);
552
+ const password = param.slice(1, -1); // Remove the first and last characters
446
553
  this.log.debug('setpassword', param, password);
447
554
  await this.matterbridge.nodeContext?.set('password', password);
448
555
  res.json({ message: 'Command received' });
449
556
  return;
450
557
  }
558
+ // Handle the command setbridgemode from Settings
451
559
  if (command === 'setbridgemode') {
452
560
  this.log.debug(`setbridgemode: ${param}`);
453
561
  this.wssSendRestartRequired();
@@ -455,6 +563,7 @@ export class Frontend {
455
563
  res.json({ message: 'Command received' });
456
564
  return;
457
565
  }
566
+ // Handle the command backup from Settings
458
567
  if (command === 'backup') {
459
568
  this.log.notice(`Prepairing the backup...`);
460
569
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -462,25 +571,26 @@ export class Frontend {
462
571
  res.json({ message: 'Command received' });
463
572
  return;
464
573
  }
574
+ // Handle the command setmbloglevel from Settings
465
575
  if (command === 'setmbloglevel') {
466
576
  this.log.debug('Matterbridge log level:', param);
467
577
  if (param === 'Debug') {
468
- this.log.logLevel = "debug";
578
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
469
579
  }
470
580
  else if (param === 'Info') {
471
- this.log.logLevel = "info";
581
+ this.log.logLevel = "info" /* LogLevel.INFO */;
472
582
  }
473
583
  else if (param === 'Notice') {
474
- this.log.logLevel = "notice";
584
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
475
585
  }
476
586
  else if (param === 'Warn') {
477
- this.log.logLevel = "warn";
587
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
478
588
  }
479
589
  else if (param === 'Error') {
480
- this.log.logLevel = "error";
590
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
481
591
  }
482
592
  else if (param === 'Fatal') {
483
- this.log.logLevel = "fatal";
593
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
484
594
  }
485
595
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
486
596
  this.matterbridge.log.logLevel = this.log.logLevel;
@@ -490,12 +600,13 @@ export class Frontend {
490
600
  for (const plugin of this.matterbridge.plugins) {
491
601
  if (!plugin.platform || !plugin.platform.config)
492
602
  continue;
493
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
494
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
603
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
604
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
495
605
  }
496
606
  res.json({ message: 'Command received' });
497
607
  return;
498
608
  }
609
+ // Handle the command setmbloglevel from Settings
499
610
  if (command === 'setmjloglevel') {
500
611
  this.log.debug('Matter.js log level:', param);
501
612
  if (param === 'Debug') {
@@ -520,30 +631,34 @@ export class Frontend {
520
631
  res.json({ message: 'Command received' });
521
632
  return;
522
633
  }
634
+ // Handle the command setmdnsinterface from Settings
523
635
  if (command === 'setmdnsinterface') {
524
- param = param.slice(1, -1);
636
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
525
637
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
526
638
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
527
639
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
528
640
  res.json({ message: 'Command received' });
529
641
  return;
530
642
  }
643
+ // Handle the command setipv4address from Settings
531
644
  if (command === 'setipv4address') {
532
- param = param.slice(1, -1);
645
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
533
646
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
534
647
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
535
648
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
536
649
  res.json({ message: 'Command received' });
537
650
  return;
538
651
  }
652
+ // Handle the command setipv6address from Settings
539
653
  if (command === 'setipv6address') {
540
- param = param.slice(1, -1);
654
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
541
655
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
542
656
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
543
657
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
544
658
  res.json({ message: 'Command received' });
545
659
  return;
546
660
  }
661
+ // Handle the command setmatterport from Settings
547
662
  if (command === 'setmatterport') {
548
663
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
549
664
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -552,6 +667,7 @@ export class Frontend {
552
667
  res.json({ message: 'Command received' });
553
668
  return;
554
669
  }
670
+ // Handle the command setmatterdiscriminator from Settings
555
671
  if (command === 'setmatterdiscriminator') {
556
672
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
557
673
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -560,6 +676,7 @@ export class Frontend {
560
676
  res.json({ message: 'Command received' });
561
677
  return;
562
678
  }
679
+ // Handle the command setmatterpasscode from Settings
563
680
  if (command === 'setmatterpasscode') {
564
681
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
565
682
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -568,17 +685,20 @@ export class Frontend {
568
685
  res.json({ message: 'Command received' });
569
686
  return;
570
687
  }
688
+ // Handle the command setmbloglevel from Settings
571
689
  if (command === 'setmblogfile') {
572
690
  this.log.debug('Matterbridge file log:', param);
573
691
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
574
692
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
693
+ // Create the file logger for matterbridge
575
694
  if (param === 'true')
576
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
695
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
577
696
  else
578
697
  AnsiLogger.setGlobalLogfile(undefined);
579
698
  res.json({ message: 'Command received' });
580
699
  return;
581
700
  }
701
+ // Handle the command setmbloglevel from Settings
582
702
  if (command === 'setmjlogfile') {
583
703
  this.log.debug('Matter file log:', param);
584
704
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -605,40 +725,48 @@ export class Frontend {
605
725
  res.json({ message: 'Command received' });
606
726
  return;
607
727
  }
728
+ // Handle the command unregister from Settings
608
729
  if (command === 'unregister') {
609
730
  await this.matterbridge.unregisterAndShutdownProcess();
610
731
  res.json({ message: 'Command received' });
611
732
  return;
612
733
  }
734
+ // Handle the command reset from Settings
613
735
  if (command === 'reset') {
614
736
  await this.matterbridge.shutdownProcessAndReset();
615
737
  res.json({ message: 'Command received' });
616
738
  return;
617
739
  }
740
+ // Handle the command factoryreset from Settings
618
741
  if (command === 'factoryreset') {
619
742
  await this.matterbridge.shutdownProcessAndFactoryReset();
620
743
  res.json({ message: 'Command received' });
621
744
  return;
622
745
  }
746
+ // Handle the command shutdown from Header
623
747
  if (command === 'shutdown') {
624
748
  await this.matterbridge.shutdownProcess();
625
749
  res.json({ message: 'Command received' });
626
750
  return;
627
751
  }
752
+ // Handle the command restart from Header
628
753
  if (command === 'restart') {
629
754
  await this.matterbridge.restartProcess();
630
755
  res.json({ message: 'Command received' });
631
756
  return;
632
757
  }
758
+ // Handle the command update from Header
633
759
  if (command === 'update') {
634
760
  await this.matterbridge.updateProcess();
635
761
  this.wssSendRestartRequired();
636
762
  res.json({ message: 'Command received' });
637
763
  return;
638
764
  }
765
+ // Handle the command saveconfig from Home
639
766
  if (command === 'saveconfig') {
640
767
  param = param.replace(/\*/g, '\\');
641
768
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
769
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
642
770
  if (!this.matterbridge.plugins.has(param)) {
643
771
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
644
772
  }
@@ -652,49 +780,58 @@ export class Frontend {
652
780
  res.json({ message: 'Command received' });
653
781
  return;
654
782
  }
783
+ // Handle the command installplugin from Home
655
784
  if (command === 'installplugin') {
656
785
  param = param.replace(/\*/g, '\\');
657
786
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
658
787
  try {
659
788
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
660
789
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
790
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
661
791
  }
662
792
  catch (error) {
663
793
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
664
794
  }
665
795
  this.wssSendRestartRequired();
666
796
  param = param.split('@')[0];
797
+ // Also add the plugin to matterbridge so no return!
667
798
  if (param === 'matterbridge') {
799
+ // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
668
800
  res.json({ message: 'Command received' });
669
801
  return;
670
802
  }
671
803
  }
804
+ // Handle the command addplugin from Home
672
805
  if (command === 'addplugin' || command === 'installplugin') {
673
806
  param = param.replace(/\*/g, '\\');
674
807
  const plugin = await this.matterbridge.plugins.add(param);
675
808
  if (plugin) {
676
809
  if (this.matterbridge.bridgeMode === 'childbridge') {
810
+ // We don't know now if the plugin is a dynamic platform or an accessory platform so we create the server node and the aggregator node
811
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
677
812
  this.matterbridge.createDynamicPlugin(plugin, true);
678
813
  }
679
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
814
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
680
815
  }
681
816
  res.json({ message: 'Command received' });
682
817
  this.wssSendRefreshRequired();
683
818
  return;
684
819
  }
820
+ // Handle the command removeplugin from Home
685
821
  if (command === 'removeplugin') {
686
822
  if (!this.matterbridge.plugins.has(param)) {
687
823
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
688
824
  }
689
825
  else {
690
826
  const plugin = this.matterbridge.plugins.get(param);
691
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
827
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
692
828
  await this.matterbridge.plugins.remove(param);
693
829
  }
694
830
  res.json({ message: 'Command received' });
695
831
  this.wssSendRefreshRequired();
696
832
  return;
697
833
  }
834
+ // Handle the command enableplugin from Home
698
835
  if (command === 'enableplugin') {
699
836
  if (!this.matterbridge.plugins.has(param)) {
700
837
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -712,15 +849,17 @@ export class Frontend {
712
849
  plugin.addedDevices = undefined;
713
850
  await this.matterbridge.plugins.enable(param);
714
851
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
852
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
715
853
  this.matterbridge.createDynamicPlugin(plugin, true);
716
854
  }
717
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
855
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background since the server node is already started
718
856
  }
719
857
  }
720
858
  res.json({ message: 'Command received' });
721
859
  this.wssSendRefreshRequired();
722
860
  return;
723
861
  }
862
+ // Handle the command disableplugin from Home
724
863
  if (command === 'disableplugin') {
725
864
  if (!this.matterbridge.plugins.has(param)) {
726
865
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -728,7 +867,7 @@ export class Frontend {
728
867
  else {
729
868
  const plugin = this.matterbridge.plugins.get(param);
730
869
  if (plugin && plugin.enabled) {
731
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
870
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
732
871
  await this.matterbridge.plugins.disable(param);
733
872
  }
734
873
  }
@@ -737,6 +876,7 @@ export class Frontend {
737
876
  return;
738
877
  }
739
878
  });
879
+ // Fallback for routing (must be the last route)
740
880
  this.expressApp.get('*', (req, res) => {
741
881
  this.log.debug('The frontend sent:', req.url);
742
882
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -745,24 +885,29 @@ export class Frontend {
745
885
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
746
886
  }
747
887
  async stop() {
888
+ // Close the http server
748
889
  if (this.httpServer) {
749
890
  this.httpServer.close();
750
891
  this.httpServer.removeAllListeners();
751
892
  this.httpServer = undefined;
752
893
  this.log.debug('Frontend http server closed successfully');
753
894
  }
895
+ // Close the https server
754
896
  if (this.httpsServer) {
755
897
  this.httpsServer.close();
756
898
  this.httpsServer.removeAllListeners();
757
899
  this.httpsServer = undefined;
758
900
  this.log.debug('Frontend https server closed successfully');
759
901
  }
902
+ // Remove listeners from the express app
760
903
  if (this.expressApp) {
761
904
  this.expressApp.removeAllListeners();
762
905
  this.expressApp = undefined;
763
906
  this.log.debug('Frontend app closed successfully');
764
907
  }
908
+ // Close the WebSocket server
765
909
  if (this.webSocketServer) {
910
+ // Close all active connections
766
911
  this.webSocketServer.clients.forEach((client) => {
767
912
  if (client.readyState === WebSocket.OPEN) {
768
913
  client.close();
@@ -778,10 +923,12 @@ export class Frontend {
778
923
  });
779
924
  this.webSocketServer = undefined;
780
925
  }
926
+ // Stop the memory dump interval
781
927
  if (hasParameter('memorydump')) {
782
928
  this.stopCpuMemoryDump();
783
929
  }
784
930
  }
931
+ // Function to format bytes to KB or MB
785
932
  formatMemoryUsage = (bytes) => {
786
933
  const kb = bytes / 1024;
787
934
  const mb = kb / 1024;
@@ -793,9 +940,10 @@ export class Frontend {
793
940
  const interval = () => {
794
941
  const currCpus = os.cpus();
795
942
  if (currCpus.length !== this.prevCpus.length) {
796
- this.prevCpus = currCpus;
943
+ this.prevCpus = currCpus; // Reset the previous cpus if the number of cpu has changed
797
944
  }
798
945
  let totalIdle = 0, totalTick = 0;
946
+ // Get the cpu usage
799
947
  this.prevCpus.forEach((prevCpu, i) => {
800
948
  const currCpu = currCpus[i];
801
949
  const idleDiff = currCpu.times.idle - prevCpu.times.idle;
@@ -805,6 +953,7 @@ export class Frontend {
805
953
  });
806
954
  const cpuUsage = (100 - (totalIdle / totalTick) * 100).toFixed(2);
807
955
  this.prevCpus = currCpus;
956
+ // Get the memory usage
808
957
  const memoryUsageRaw = process.memoryUsage();
809
958
  this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
810
959
  const memoryUsage = {
@@ -837,14 +986,21 @@ export class Frontend {
837
986
  external: this.formatMemoryUsage(memory.external),
838
987
  arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
839
988
  };
989
+ // eslint-disable-next-line no-console
840
990
  console.log(`${YELLOW}Cpu usage${db} ${CYAN}${memory.cpu.padStart(6, ' ')} %${db} - ${YELLOW}Memory usage${db} rss ${CYAN}${memoryUsage.rss}${db} heapTotal ${CYAN}${memoryUsage.heapTotal}${db} heapUsed ${CYAN}${memoryUsage.heapUsed}${db} external ${memoryUsage.external} arrayBuffers ${memoryUsage.arrayBuffers}${rs}`);
841
991
  }
842
992
  this.memoryData = [];
843
993
  this.prevCpus = [];
844
994
  }
995
+ /**
996
+ * Retrieves the api settings data.
997
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
998
+ */
845
999
  async getApiSettings() {
1000
+ // Update the system information
846
1001
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
847
1002
  this.matterbridge.systemInformation.heap = this.formatMemoryUsage(process.memoryUsage().heapUsed) + ' / ' + this.formatMemoryUsage(process.memoryUsage().heapTotal);
1003
+ // Update the matterbridge information
848
1004
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
849
1005
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
850
1006
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -863,6 +1019,11 @@ export class Frontend {
863
1019
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
864
1020
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
865
1021
  }
1022
+ /**
1023
+ * Retrieves the cluster text description from a given device.
1024
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1025
+ * @returns {string} The attributes description of the cluster servers in the device.
1026
+ */
866
1027
  getClusterTextFromDevice(device) {
867
1028
  const getAttribute = (device, cluster, attribute) => {
868
1029
  let value = undefined;
@@ -901,6 +1062,7 @@ export class Frontend {
901
1062
  };
902
1063
  let attributes = '';
903
1064
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1065
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
904
1066
  if (typeof attributeValue === 'undefined')
905
1067
  return;
906
1068
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -978,8 +1140,13 @@ export class Frontend {
978
1140
  if (clusterName === 'userLabel' && attributeName === 'labelList')
979
1141
  attributes += `${getUserLabel(device)} `;
980
1142
  });
1143
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
981
1144
  return attributes.trimStart().trimEnd();
982
1145
  }
1146
+ /**
1147
+ * Retrieves the base registered plugins sanitized for res.json().
1148
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1149
+ */
983
1150
  getBaseRegisteredPlugins() {
984
1151
  const baseRegisteredPlugins = [];
985
1152
  for (const plugin of this.matterbridge.plugins) {
@@ -1010,6 +1177,14 @@ export class Frontend {
1010
1177
  }
1011
1178
  return baseRegisteredPlugins;
1012
1179
  }
1180
+ /**
1181
+ * Handles incoming websocket messages for the Matterbridge.
1182
+ *
1183
+ * @param {Matterbridge} this - The Matterbridge instance.
1184
+ * @param {WebSocket} client - The websocket client that sent the message.
1185
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1186
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1187
+ */
1013
1188
  async wsMessageHandler(client, message) {
1014
1189
  let data;
1015
1190
  try {
@@ -1097,8 +1272,10 @@ export class Frontend {
1097
1272
  else if (data.method === '/api/devices') {
1098
1273
  const devices = [];
1099
1274
  this.matterbridge.devices.forEach(async (device) => {
1275
+ // Filter by pluginName if provided
1100
1276
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1101
1277
  return;
1278
+ // Check if the device has the required properties
1102
1279
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
1103
1280
  return;
1104
1281
  const cluster = this.getClusterTextFromDevice(device);
@@ -1181,6 +1358,7 @@ export class Frontend {
1181
1358
  });
1182
1359
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1183
1360
  deviceTypes = [];
1361
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1184
1362
  const name = childEndpoint.endpoint?.id;
1185
1363
  const clusterServers = childEndpoint.getAllClusterServers();
1186
1364
  clusterServers.forEach((clusterServer) => {
@@ -1263,44 +1441,73 @@ export class Frontend {
1263
1441
  return;
1264
1442
  }
1265
1443
  }
1444
+ /**
1445
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1446
+ *
1447
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1448
+ * @param {string} time - The time string of the message
1449
+ * @param {string} name - The logger name of the message
1450
+ * @param {string} message - The content of the message.
1451
+ */
1266
1452
  wssSendMessage(level, time, name, message) {
1267
1453
  if (!level || !time || !name || !message)
1268
1454
  return;
1455
+ // Remove ANSI escape codes from the message
1456
+ // eslint-disable-next-line no-control-regex
1269
1457
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1458
+ // Remove leading asterisks from the message
1270
1459
  message = message.replace(/^\*+/, '');
1460
+ // Replace all occurrences of \t and \n
1271
1461
  message = message.replace(/[\t\n]/g, '');
1462
+ // Remove non-printable characters
1463
+ // eslint-disable-next-line no-control-regex
1272
1464
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1465
+ // Replace all occurrences of \" with "
1273
1466
  message = message.replace(/\\"/g, '"');
1467
+ // Define the maximum allowed length for continuous characters without a space
1274
1468
  const maxContinuousLength = 100;
1275
1469
  const keepStartLength = 20;
1276
1470
  const keepEndLength = 20;
1471
+ // Split the message into words
1277
1472
  message = message
1278
1473
  .split(' ')
1279
1474
  .map((word) => {
1475
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1280
1476
  if (word.length > maxContinuousLength) {
1281
1477
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1282
1478
  }
1283
1479
  return word;
1284
1480
  })
1285
1481
  .join(' ');
1482
+ // Send the message to all connected clients
1286
1483
  this.webSocketServer?.clients.forEach((client) => {
1287
1484
  if (client.readyState === WebSocket.OPEN) {
1288
1485
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1289
1486
  }
1290
1487
  });
1291
1488
  }
1489
+ /**
1490
+ * Sends a need to refresh WebSocket message to all connected clients.
1491
+ *
1492
+ */
1292
1493
  wssSendRefreshRequired() {
1293
1494
  this.log.debug('Sending a refresh required message to all connected clients');
1294
1495
  this.matterbridge.matterbridgeInformation.refreshRequired = true;
1496
+ // Send the message to all connected clients
1295
1497
  this.webSocketServer?.clients.forEach((client) => {
1296
1498
  if (client.readyState === WebSocket.OPEN) {
1297
1499
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1298
1500
  }
1299
1501
  });
1300
1502
  }
1503
+ /**
1504
+ * Sends a need to restart WebSocket message to all connected clients.
1505
+ *
1506
+ */
1301
1507
  wssSendRestartRequired() {
1302
1508
  this.log.debug('Sending a restart required message to all connected clients');
1303
1509
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1510
+ // Send the message to all connected clients
1304
1511
  this.webSocketServer?.clients.forEach((client) => {
1305
1512
  if (client.readyState === WebSocket.OPEN) {
1306
1513
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
@@ -1308,3 +1515,4 @@ export class Frontend {
1308
1515
  });
1309
1516
  }
1310
1517
  }
1518
+ //# sourceMappingURL=frontend.js.map