matterbridge 2.1.4-dev.3 → 2.1.4

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 (112) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/README-DOCKER.md +11 -7
  3. package/dist/cli.d.ts +25 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +26 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cluster/export.d.ts +2 -0
  8. package/dist/cluster/export.d.ts.map +1 -0
  9. package/dist/cluster/export.js +2 -0
  10. package/dist/cluster/export.js.map +1 -0
  11. package/dist/defaultConfigSchema.d.ts +27 -0
  12. package/dist/defaultConfigSchema.d.ts.map +1 -0
  13. package/dist/defaultConfigSchema.js +23 -0
  14. package/dist/defaultConfigSchema.js.map +1 -0
  15. package/dist/deviceManager.d.ts +114 -0
  16. package/dist/deviceManager.d.ts.map +1 -0
  17. package/dist/deviceManager.js +94 -1
  18. package/dist/deviceManager.js.map +1 -0
  19. package/dist/frontend.d.ts +110 -0
  20. package/dist/frontend.d.ts.map +1 -0
  21. package/dist/frontend.js +232 -23
  22. package/dist/frontend.js.map +1 -0
  23. package/dist/index.d.ts +35 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +28 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/logger/export.d.ts +2 -0
  28. package/dist/logger/export.d.ts.map +1 -0
  29. package/dist/logger/export.js +1 -0
  30. package/dist/logger/export.js.map +1 -0
  31. package/dist/matter/behaviors.d.ts +2 -0
  32. package/dist/matter/behaviors.d.ts.map +1 -0
  33. package/dist/matter/behaviors.js +2 -0
  34. package/dist/matter/behaviors.js.map +1 -0
  35. package/dist/matter/clusters.d.ts +2 -0
  36. package/dist/matter/clusters.d.ts.map +1 -0
  37. package/dist/matter/clusters.js +2 -0
  38. package/dist/matter/clusters.js.map +1 -0
  39. package/dist/matter/devices.d.ts +2 -0
  40. package/dist/matter/devices.d.ts.map +1 -0
  41. package/dist/matter/devices.js +2 -0
  42. package/dist/matter/devices.js.map +1 -0
  43. package/dist/matter/endpoints.d.ts +2 -0
  44. package/dist/matter/endpoints.d.ts.map +1 -0
  45. package/dist/matter/endpoints.js +2 -0
  46. package/dist/matter/endpoints.js.map +1 -0
  47. package/dist/matter/export.d.ts +5 -0
  48. package/dist/matter/export.d.ts.map +1 -0
  49. package/dist/matter/export.js +2 -0
  50. package/dist/matter/export.js.map +1 -0
  51. package/dist/matter/types.d.ts +3 -0
  52. package/dist/matter/types.d.ts.map +1 -0
  53. package/dist/matter/types.js +2 -0
  54. package/dist/matter/types.js.map +1 -0
  55. package/dist/matterbridge.d.ts +409 -0
  56. package/dist/matterbridge.d.ts.map +1 -0
  57. package/dist/matterbridge.js +749 -39
  58. package/dist/matterbridge.js.map +1 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  60. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  61. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  62. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  63. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  64. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  65. package/dist/matterbridgeBehaviors.js +32 -1
  66. package/dist/matterbridgeBehaviors.js.map +1 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  68. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  69. package/dist/matterbridgeDeviceTypes.js +112 -11
  70. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  72. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  73. package/dist/matterbridgeDynamicPlatform.js +33 -0
  74. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  75. package/dist/matterbridgeEndpoint.d.ts +834 -0
  76. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  77. package/dist/matterbridgeEndpoint.js +691 -6
  78. package/dist/matterbridgeEndpoint.js.map +1 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts +2264 -0
  80. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  81. package/dist/matterbridgeEndpointHelpers.js +105 -9
  82. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  83. package/dist/matterbridgePlatform.d.ts +159 -0
  84. package/dist/matterbridgePlatform.d.ts.map +1 -0
  85. package/dist/matterbridgePlatform.js +122 -6
  86. package/dist/matterbridgePlatform.js.map +1 -0
  87. package/dist/matterbridgeTypes.d.ts +167 -0
  88. package/dist/matterbridgeTypes.d.ts.map +1 -0
  89. package/dist/matterbridgeTypes.js +24 -0
  90. package/dist/matterbridgeTypes.js.map +1 -0
  91. package/dist/pluginManager.d.ts +236 -0
  92. package/dist/pluginManager.d.ts.map +1 -0
  93. package/dist/pluginManager.js +230 -3
  94. package/dist/pluginManager.js.map +1 -0
  95. package/dist/storage/export.d.ts +2 -0
  96. package/dist/storage/export.d.ts.map +1 -0
  97. package/dist/storage/export.js +1 -0
  98. package/dist/storage/export.js.map +1 -0
  99. package/dist/utils/colorUtils.d.ts +61 -0
  100. package/dist/utils/colorUtils.d.ts.map +1 -0
  101. package/dist/utils/colorUtils.js +205 -2
  102. package/dist/utils/colorUtils.js.map +1 -0
  103. package/dist/utils/export.d.ts +3 -0
  104. package/dist/utils/export.d.ts.map +1 -0
  105. package/dist/utils/export.js +1 -0
  106. package/dist/utils/export.js.map +1 -0
  107. package/dist/utils/utils.d.ts +231 -0
  108. package/dist/utils/utils.d.ts.map +1 -0
  109. package/dist/utils/utils.js +307 -9
  110. package/dist/utils/utils.js.map +1 -0
  111. package/npm-shrinkwrap.json +2 -2
  112. 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,6 +885,7 @@ 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
+ // Start the memory check. This will not allow the process to exit but will log the memory usage for 5 minutes.
748
889
  if (hasParameter('memorycheck')) {
749
890
  await new Promise((resolve) => {
750
891
  this.log.debug(`***Memory check started for ${getIntParameter('memorycheck') ?? 5 * 60 * 1000} ms`);
@@ -754,24 +895,29 @@ export class Frontend {
754
895
  }, getIntParameter('memorycheck') ?? 5 * 60 * 1000);
755
896
  });
756
897
  }
898
+ // Close the http server
757
899
  if (this.httpServer) {
758
900
  this.httpServer.close();
759
901
  this.httpServer.removeAllListeners();
760
902
  this.httpServer = undefined;
761
903
  this.log.debug('Frontend http server closed successfully');
762
904
  }
905
+ // Close the https server
763
906
  if (this.httpsServer) {
764
907
  this.httpsServer.close();
765
908
  this.httpsServer.removeAllListeners();
766
909
  this.httpsServer = undefined;
767
910
  this.log.debug('Frontend https server closed successfully');
768
911
  }
912
+ // Remove listeners from the express app
769
913
  if (this.expressApp) {
770
914
  this.expressApp.removeAllListeners();
771
915
  this.expressApp = undefined;
772
916
  this.log.debug('Frontend app closed successfully');
773
917
  }
918
+ // Close the WebSocket server
774
919
  if (this.webSocketServer) {
920
+ // Close all active connections
775
921
  this.webSocketServer.clients.forEach((client) => {
776
922
  if (client.readyState === WebSocket.OPEN) {
777
923
  client.close();
@@ -787,10 +933,12 @@ export class Frontend {
787
933
  });
788
934
  this.webSocketServer = undefined;
789
935
  }
936
+ // Stop the memory dump interval
790
937
  if (hasParameter('memorydump')) {
791
938
  this.stopCpuMemoryDump();
792
939
  }
793
940
  }
941
+ // Function to format bytes to KB or MB
794
942
  formatMemoryUsage = (bytes) => {
795
943
  const kb = bytes / 1024;
796
944
  const mb = kb / 1024;
@@ -802,9 +950,10 @@ export class Frontend {
802
950
  const interval = () => {
803
951
  const currCpus = os.cpus();
804
952
  if (currCpus.length !== this.prevCpus.length) {
805
- this.prevCpus = currCpus;
953
+ this.prevCpus = currCpus; // Reset the previous cpus if the number of cpu has changed
806
954
  }
807
955
  let totalIdle = 0, totalTick = 0;
956
+ // Get the cpu usage
808
957
  this.prevCpus.forEach((prevCpu, i) => {
809
958
  const currCpu = currCpus[i];
810
959
  const idleDiff = currCpu.times.idle - prevCpu.times.idle;
@@ -814,6 +963,7 @@ export class Frontend {
814
963
  });
815
964
  const cpuUsage = (100 - (totalIdle / totalTick) * 100).toFixed(2);
816
965
  this.prevCpus = currCpus;
966
+ // Get the memory usage
817
967
  const memoryUsageRaw = process.memoryUsage();
818
968
  this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
819
969
  const memoryUsage = {
@@ -846,14 +996,21 @@ export class Frontend {
846
996
  external: this.formatMemoryUsage(memory.external),
847
997
  arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
848
998
  };
999
+ // eslint-disable-next-line no-console
849
1000
  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}`);
850
1001
  }
851
1002
  this.memoryData = [];
852
1003
  this.prevCpus = [];
853
1004
  }
1005
+ /**
1006
+ * Retrieves the api settings data.
1007
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1008
+ */
854
1009
  async getApiSettings() {
1010
+ // Update the system information
855
1011
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
856
1012
  this.matterbridge.systemInformation.heap = this.formatMemoryUsage(process.memoryUsage().heapUsed) + ' / ' + this.formatMemoryUsage(process.memoryUsage().heapTotal);
1013
+ // Update the matterbridge information
857
1014
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
858
1015
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
859
1016
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -872,6 +1029,11 @@ export class Frontend {
872
1029
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
873
1030
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
874
1031
  }
1032
+ /**
1033
+ * Retrieves the cluster text description from a given device.
1034
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1035
+ * @returns {string} The attributes description of the cluster servers in the device.
1036
+ */
875
1037
  getClusterTextFromDevice(device) {
876
1038
  const getAttribute = (device, cluster, attribute) => {
877
1039
  let value = undefined;
@@ -910,6 +1072,7 @@ export class Frontend {
910
1072
  };
911
1073
  let attributes = '';
912
1074
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1075
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
913
1076
  if (typeof attributeValue === 'undefined')
914
1077
  return;
915
1078
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -987,8 +1150,13 @@ export class Frontend {
987
1150
  if (clusterName === 'userLabel' && attributeName === 'labelList')
988
1151
  attributes += `${getUserLabel(device)} `;
989
1152
  });
1153
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
990
1154
  return attributes.trimStart().trimEnd();
991
1155
  }
1156
+ /**
1157
+ * Retrieves the base registered plugins sanitized for res.json().
1158
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1159
+ */
992
1160
  getBaseRegisteredPlugins() {
993
1161
  const baseRegisteredPlugins = [];
994
1162
  for (const plugin of this.matterbridge.plugins) {
@@ -1019,6 +1187,14 @@ export class Frontend {
1019
1187
  }
1020
1188
  return baseRegisteredPlugins;
1021
1189
  }
1190
+ /**
1191
+ * Handles incoming websocket messages for the Matterbridge.
1192
+ *
1193
+ * @param {Matterbridge} this - The Matterbridge instance.
1194
+ * @param {WebSocket} client - The websocket client that sent the message.
1195
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1196
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1197
+ */
1022
1198
  async wsMessageHandler(client, message) {
1023
1199
  let data;
1024
1200
  try {
@@ -1106,8 +1282,10 @@ export class Frontend {
1106
1282
  else if (data.method === '/api/devices') {
1107
1283
  const devices = [];
1108
1284
  this.matterbridge.devices.forEach(async (device) => {
1285
+ // Filter by pluginName if provided
1109
1286
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1110
1287
  return;
1288
+ // Check if the device has the required properties
1111
1289
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
1112
1290
  return;
1113
1291
  const cluster = this.getClusterTextFromDevice(device);
@@ -1190,6 +1368,7 @@ export class Frontend {
1190
1368
  });
1191
1369
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1192
1370
  deviceTypes = [];
1371
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1193
1372
  const name = childEndpoint.endpoint?.id;
1194
1373
  const clusterServers = childEndpoint.getAllClusterServers();
1195
1374
  clusterServers.forEach((clusterServer) => {
@@ -1272,44 +1451,73 @@ export class Frontend {
1272
1451
  return;
1273
1452
  }
1274
1453
  }
1454
+ /**
1455
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1456
+ *
1457
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1458
+ * @param {string} time - The time string of the message
1459
+ * @param {string} name - The logger name of the message
1460
+ * @param {string} message - The content of the message.
1461
+ */
1275
1462
  wssSendMessage(level, time, name, message) {
1276
1463
  if (!level || !time || !name || !message)
1277
1464
  return;
1465
+ // Remove ANSI escape codes from the message
1466
+ // eslint-disable-next-line no-control-regex
1278
1467
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1468
+ // Remove leading asterisks from the message
1279
1469
  message = message.replace(/^\*+/, '');
1470
+ // Replace all occurrences of \t and \n
1280
1471
  message = message.replace(/[\t\n]/g, '');
1472
+ // Remove non-printable characters
1473
+ // eslint-disable-next-line no-control-regex
1281
1474
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1475
+ // Replace all occurrences of \" with "
1282
1476
  message = message.replace(/\\"/g, '"');
1477
+ // Define the maximum allowed length for continuous characters without a space
1283
1478
  const maxContinuousLength = 100;
1284
1479
  const keepStartLength = 20;
1285
1480
  const keepEndLength = 20;
1481
+ // Split the message into words
1286
1482
  message = message
1287
1483
  .split(' ')
1288
1484
  .map((word) => {
1485
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1289
1486
  if (word.length > maxContinuousLength) {
1290
1487
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1291
1488
  }
1292
1489
  return word;
1293
1490
  })
1294
1491
  .join(' ');
1492
+ // Send the message to all connected clients
1295
1493
  this.webSocketServer?.clients.forEach((client) => {
1296
1494
  if (client.readyState === WebSocket.OPEN) {
1297
1495
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1298
1496
  }
1299
1497
  });
1300
1498
  }
1499
+ /**
1500
+ * Sends a need to refresh WebSocket message to all connected clients.
1501
+ *
1502
+ */
1301
1503
  wssSendRefreshRequired() {
1302
1504
  this.log.debug('Sending a refresh required message to all connected clients');
1303
1505
  this.matterbridge.matterbridgeInformation.refreshRequired = true;
1506
+ // Send the message to all connected clients
1304
1507
  this.webSocketServer?.clients.forEach((client) => {
1305
1508
  if (client.readyState === WebSocket.OPEN) {
1306
1509
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1307
1510
  }
1308
1511
  });
1309
1512
  }
1513
+ /**
1514
+ * Sends a need to restart WebSocket message to all connected clients.
1515
+ *
1516
+ */
1310
1517
  wssSendRestartRequired() {
1311
1518
  this.log.debug('Sending a restart required message to all connected clients');
1312
1519
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1520
+ // Send the message to all connected clients
1313
1521
  this.webSocketServer?.clients.forEach((client) => {
1314
1522
  if (client.readyState === WebSocket.OPEN) {
1315
1523
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
@@ -1317,3 +1525,4 @@ export class Frontend {
1317
1525
  });
1318
1526
  }
1319
1527
  }
1528
+ //# sourceMappingURL=frontend.js.map