matterbridge 2.1.5-dev.7 → 2.1.5

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 -2
  2. package/README-DOCKER.md +6 -0
  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 +143 -0
  20. package/dist/frontend.d.ts.map +1 -0
  21. package/dist/frontend.js +270 -24
  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 +750 -41
  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 +835 -0
  76. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  77. package/dist/matterbridgeEndpoint.js +690 -6
  78. package/dist/matterbridgeEndpoint.js.map +1 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
  80. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  81. package/dist/matterbridgeEndpointHelpers.js +117 -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 +121 -5
  86. package/dist/matterbridgePlatform.js.map +1 -0
  87. package/dist/matterbridgeTypes.d.ts +169 -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 +231 -4
  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 +264 -10
  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.2
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,16 +30,47 @@ 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, deepCopy, 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
+ * Websocket message ID indicating a cpu update.
56
+ * @constant {number}
57
+ */
16
58
  export const WS_ID_CPU_UPDATE = 3;
59
+ /**
60
+ * Websocket message ID indicating a memory update.
61
+ * @constant {number}
62
+ */
17
63
  export const WS_ID_MEMORY_UPDATE = 4;
64
+ /**
65
+ * Websocket message ID indicating a memory update.
66
+ * @constant {number}
67
+ */
18
68
  export const WS_ID_SNACKBAR = 5;
69
+ /**
70
+ * Initializes the frontend of Matterbridge.
71
+ *
72
+ * @param port The port number to run the frontend server on. Default is 8283.
73
+ */
19
74
  export class Frontend {
20
75
  matterbridge;
21
76
  log;
@@ -32,7 +87,7 @@ export class Frontend {
32
87
  memoryTimeout;
33
88
  constructor(matterbridge) {
34
89
  this.matterbridge = matterbridge;
35
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
90
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
36
91
  }
37
92
  set logLevel(logLevel) {
38
93
  this.log.logLevel = logLevel;
@@ -40,10 +95,21 @@ export class Frontend {
40
95
  async start(port = 8283) {
41
96
  this.port = port;
42
97
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
98
+ // Create the express app that serves the frontend
43
99
  this.expressApp = express();
100
+ // Log all requests to the server for debugging
101
+ /*
102
+ this.expressApp.use((req, res, next) => {
103
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
104
+ next();
105
+ });
106
+ */
107
+ // Serve static files from '/static' endpoint
44
108
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
45
109
  if (!hasParameter('ssl')) {
110
+ // Create an HTTP server and attach the express app
46
111
  this.httpServer = createServer(this.expressApp);
112
+ // Listen on the specified port
47
113
  if (hasParameter('ingress')) {
48
114
  this.httpServer.listen(this.port, '0.0.0.0', () => {
49
115
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -57,6 +123,7 @@ export class Frontend {
57
123
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
58
124
  });
59
125
  }
126
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
127
  this.httpServer.on('error', (error) => {
61
128
  this.log.error(`Frontend http server error listening on ${this.port}`);
62
129
  switch (error.code) {
@@ -72,6 +139,7 @@ export class Frontend {
72
139
  });
73
140
  }
74
141
  else {
142
+ // Load the SSL certificate, the private key and optionally the CA certificate
75
143
  let cert;
76
144
  try {
77
145
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -99,7 +167,9 @@ export class Frontend {
99
167
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
100
168
  }
101
169
  const serverOptions = { cert, key, ca };
170
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
102
171
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
172
+ // Listen on the specified port
103
173
  if (hasParameter('ingress')) {
104
174
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
105
175
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -113,6 +183,7 @@ export class Frontend {
113
183
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
114
184
  });
115
185
  }
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
187
  this.httpsServer.on('error', (error) => {
117
188
  this.log.error(`Frontend https server error listening on ${this.port}`);
118
189
  switch (error.code) {
@@ -129,12 +200,20 @@ export class Frontend {
129
200
  }
130
201
  if (this.initializeError)
131
202
  return;
203
+ // Create a WebSocket server and attach it to the http or https server
132
204
  const wssPort = this.port;
133
205
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
134
206
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
135
207
  this.webSocketServer.on('connection', (ws, request) => {
136
208
  const clientIp = request.socket.remoteAddress;
137
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
209
+ // Set the global logger callback for the WebSocketServer
210
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
211
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
212
+ callbackLogLevel = "info" /* LogLevel.INFO */;
213
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
214
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
215
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
216
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
138
217
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
139
218
  ws.on('message', (message) => {
140
219
  this.wsMessageHandler(ws, message);
@@ -166,9 +245,11 @@ export class Frontend {
166
245
  this.webSocketServer.on('error', (ws, error) => {
167
246
  this.log.error(`WebSocketServer error: ${error}`);
168
247
  });
248
+ // Start the memory dump interval
169
249
  if (hasParameter('memorydump')) {
170
250
  this.startCpuMemoryDump();
171
251
  }
252
+ // Endpoint to validate login code
172
253
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
173
254
  const { password } = req.body;
174
255
  this.log.debug('The frontend sent /api/login', password);
@@ -187,23 +268,27 @@ export class Frontend {
187
268
  this.log.warn('/api/login error wrong password');
188
269
  res.json({ valid: false });
189
270
  }
271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
190
272
  }
191
273
  catch (error) {
192
274
  this.log.error('/api/login error getting password');
193
275
  res.json({ valid: false });
194
276
  }
195
277
  });
278
+ // Endpoint to provide health check
196
279
  this.expressApp.get('/health', (req, res) => {
197
280
  this.log.debug('Express received /health');
198
281
  const healthStatus = {
199
- status: 'ok',
200
- uptime: process.uptime(),
201
- timestamp: new Date().toISOString(),
282
+ status: 'ok', // Indicate service is healthy
283
+ uptime: process.uptime(), // Server uptime in seconds
284
+ timestamp: new Date().toISOString(), // Current timestamp
202
285
  };
203
286
  res.status(200).json(healthStatus);
204
287
  });
288
+ // Endpoint to provide memory usage details
205
289
  this.expressApp.get('/memory', async (req, res) => {
206
290
  this.log.debug('Express received /memory');
291
+ // Memory usage from process
207
292
  const memoryUsageRaw = process.memoryUsage();
208
293
  const memoryUsage = {
209
294
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -212,10 +297,13 @@ export class Frontend {
212
297
  external: this.formatMemoryUsage(memoryUsageRaw.external),
213
298
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
214
299
  };
300
+ // V8 heap statistics
215
301
  const { default: v8 } = await import('node:v8');
216
302
  const heapStatsRaw = v8.getHeapStatistics();
217
303
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
304
+ // Format heapStats
218
305
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
306
+ // Format heapSpaces
219
307
  const heapSpaces = heapSpacesRaw.map((space) => ({
220
308
  ...space,
221
309
  space_size: this.formatMemoryUsage(space.space_size),
@@ -225,6 +313,23 @@ export class Frontend {
225
313
  }));
226
314
  const { default: module } = await import('module');
227
315
  const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
316
+ /*
317
+ if (req.query.heapdump === 'true') {
318
+ const { default: heapdump } = await import('heapdump');
319
+ const filename = `heapdump-${Date.now()}.heapsnapshot`;
320
+
321
+ heapdump.writeSnapshot(filename, (err, dumpFilename) => {
322
+ if (err) {
323
+ this.log.error(`Heap dump error: ${err.message}`);
324
+ return res.status(500).json({ error: 'Heap dump failed', details: err.message });
325
+ }
326
+
327
+ this.log.info(`Heap dump written to ${dumpFilename}`);
328
+ return res.status(200).json({ ...memoryReport, heapdump: dumpFilename });
329
+ });
330
+ return; // Exit early since heapdump response is handled inside callback
331
+ }
332
+ */
228
333
  const memoryReport = {
229
334
  memoryUsage,
230
335
  heapStats,
@@ -233,6 +338,7 @@ export class Frontend {
233
338
  };
234
339
  res.status(200).json(memoryReport);
235
340
  });
341
+ // Endpoint to start advertising the server node
236
342
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
237
343
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
238
344
  if (pairingCodes) {
@@ -243,19 +349,24 @@ export class Frontend {
243
349
  res.status(500).json({ error: 'Failed to generate pairing codes' });
244
350
  }
245
351
  });
352
+ // Endpoint to provide settings
246
353
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
247
354
  this.log.debug('The frontend sent /api/settings');
248
355
  res.json(await this.getApiSettings());
249
356
  });
357
+ // Endpoint to provide plugins
250
358
  this.expressApp.get('/api/plugins', async (req, res) => {
251
359
  this.log.debug('The frontend sent /api/plugins');
252
360
  const response = this.getBaseRegisteredPlugins();
361
+ // this.log.debug('Response:', debugStringify(response));
253
362
  res.json(response);
254
363
  });
364
+ // Endpoint to provide devices
255
365
  this.expressApp.get('/api/devices', (req, res) => {
256
366
  this.log.debug('The frontend sent /api/devices');
257
367
  const devices = [];
258
368
  this.matterbridge.devices.forEach(async (device) => {
369
+ // Check if the device has the required properties
259
370
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
260
371
  return;
261
372
  const cluster = this.getClusterTextFromDevice(device);
@@ -271,8 +382,10 @@ export class Frontend {
271
382
  cluster: cluster,
272
383
  });
273
384
  });
385
+ // this.log.debug('Response:', debugStringify(data));
274
386
  res.json(devices);
275
387
  });
388
+ // Endpoint to provide the cluster servers of the devices
276
389
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
277
390
  const selectedPluginName = req.params.selectedPluginName;
278
391
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -345,6 +458,7 @@ export class Frontend {
345
458
  });
346
459
  res.json(data);
347
460
  });
461
+ // Endpoint to view the log
348
462
  this.expressApp.get('/api/view-log', async (req, res) => {
349
463
  this.log.debug('The frontend sent /api/log');
350
464
  try {
@@ -357,10 +471,12 @@ export class Frontend {
357
471
  res.status(500).send('Error reading log file');
358
472
  }
359
473
  });
474
+ // Endpoint to download the matterbridge log
360
475
  this.expressApp.get('/api/download-mblog', async (req, res) => {
361
476
  this.log.debug('The frontend sent /api/download-mblog');
362
477
  try {
363
478
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
479
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
364
480
  }
365
481
  catch (error) {
366
482
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -372,10 +488,12 @@ export class Frontend {
372
488
  }
373
489
  });
374
490
  });
491
+ // Endpoint to download the matter log
375
492
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
376
493
  this.log.debug('The frontend sent /api/download-mjlog');
377
494
  try {
378
495
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
496
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
379
497
  }
380
498
  catch (error) {
381
499
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -387,6 +505,7 @@ export class Frontend {
387
505
  }
388
506
  });
389
507
  });
508
+ // Endpoint to download the matter storage file
390
509
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
391
510
  this.log.debug('The frontend sent /api/download-mjstorage');
392
511
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -397,6 +516,7 @@ export class Frontend {
397
516
  }
398
517
  });
399
518
  });
519
+ // Endpoint to download the matterbridge storage directory
400
520
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
401
521
  this.log.debug('The frontend sent /api/download-mbstorage');
402
522
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -407,6 +527,7 @@ export class Frontend {
407
527
  }
408
528
  });
409
529
  });
530
+ // Endpoint to download the matterbridge plugin directory
410
531
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
411
532
  this.log.debug('The frontend sent /api/download-pluginstorage');
412
533
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -417,9 +538,11 @@ export class Frontend {
417
538
  }
418
539
  });
419
540
  });
541
+ // Endpoint to download the matterbridge plugin config files
420
542
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
421
543
  this.log.debug('The frontend sent /api/download-pluginconfig');
422
544
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
545
+ // 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')));
423
546
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
424
547
  if (error) {
425
548
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -427,6 +550,7 @@ export class Frontend {
427
550
  }
428
551
  });
429
552
  });
553
+ // Endpoint to download the matterbridge plugin config files
430
554
  this.expressApp.get('/api/download-backup', async (req, res) => {
431
555
  this.log.debug('The frontend sent /api/download-backup');
432
556
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -436,6 +560,7 @@ export class Frontend {
436
560
  }
437
561
  });
438
562
  });
563
+ // Endpoint to receive commands
439
564
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
440
565
  const command = req.params.command;
441
566
  let param = req.params.param;
@@ -445,13 +570,15 @@ export class Frontend {
445
570
  return;
446
571
  }
447
572
  this.log.debug(`Received frontend command: ${command}:${param}`);
573
+ // Handle the command setpassword from Settings
448
574
  if (command === 'setpassword') {
449
- const password = param.slice(1, -1);
575
+ const password = param.slice(1, -1); // Remove the first and last characters
450
576
  this.log.debug('setpassword', param, password);
451
577
  await this.matterbridge.nodeContext?.set('password', password);
452
578
  res.json({ message: 'Command received' });
453
579
  return;
454
580
  }
581
+ // Handle the command setbridgemode from Settings
455
582
  if (command === 'setbridgemode') {
456
583
  this.log.debug(`setbridgemode: ${param}`);
457
584
  this.wssSendRestartRequired();
@@ -459,6 +586,7 @@ export class Frontend {
459
586
  res.json({ message: 'Command received' });
460
587
  return;
461
588
  }
589
+ // Handle the command backup from Settings
462
590
  if (command === 'backup') {
463
591
  this.log.notice(`Prepairing the backup...`);
464
592
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -467,25 +595,26 @@ export class Frontend {
467
595
  res.json({ message: 'Command received' });
468
596
  return;
469
597
  }
598
+ // Handle the command setmbloglevel from Settings
470
599
  if (command === 'setmbloglevel') {
471
600
  this.log.debug('Matterbridge log level:', param);
472
601
  if (param === 'Debug') {
473
- this.log.logLevel = "debug";
602
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
474
603
  }
475
604
  else if (param === 'Info') {
476
- this.log.logLevel = "info";
605
+ this.log.logLevel = "info" /* LogLevel.INFO */;
477
606
  }
478
607
  else if (param === 'Notice') {
479
- this.log.logLevel = "notice";
608
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
480
609
  }
481
610
  else if (param === 'Warn') {
482
- this.log.logLevel = "warn";
611
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
483
612
  }
484
613
  else if (param === 'Error') {
485
- this.log.logLevel = "error";
614
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
486
615
  }
487
616
  else if (param === 'Fatal') {
488
- this.log.logLevel = "fatal";
617
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
489
618
  }
490
619
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
491
620
  this.matterbridge.log.logLevel = this.log.logLevel;
@@ -495,12 +624,13 @@ export class Frontend {
495
624
  for (const plugin of this.matterbridge.plugins) {
496
625
  if (!plugin.platform || !plugin.platform.config)
497
626
  continue;
498
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
499
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
627
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
628
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
500
629
  }
501
630
  res.json({ message: 'Command received' });
502
631
  return;
503
632
  }
633
+ // Handle the command setmbloglevel from Settings
504
634
  if (command === 'setmjloglevel') {
505
635
  this.log.debug('Matter.js log level:', param);
506
636
  if (param === 'Debug') {
@@ -525,30 +655,34 @@ export class Frontend {
525
655
  res.json({ message: 'Command received' });
526
656
  return;
527
657
  }
658
+ // Handle the command setmdnsinterface from Settings
528
659
  if (command === 'setmdnsinterface') {
529
- param = param.slice(1, -1);
660
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
530
661
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
531
662
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
532
663
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
533
664
  res.json({ message: 'Command received' });
534
665
  return;
535
666
  }
667
+ // Handle the command setipv4address from Settings
536
668
  if (command === 'setipv4address') {
537
- param = param.slice(1, -1);
669
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
538
670
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
539
671
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
540
672
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
541
673
  res.json({ message: 'Command received' });
542
674
  return;
543
675
  }
676
+ // Handle the command setipv6address from Settings
544
677
  if (command === 'setipv6address') {
545
- param = param.slice(1, -1);
678
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
546
679
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
547
680
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
548
681
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
549
682
  res.json({ message: 'Command received' });
550
683
  return;
551
684
  }
685
+ // Handle the command setmatterport from Settings
552
686
  if (command === 'setmatterport') {
553
687
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
554
688
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -557,6 +691,7 @@ export class Frontend {
557
691
  res.json({ message: 'Command received' });
558
692
  return;
559
693
  }
694
+ // Handle the command setmatterdiscriminator from Settings
560
695
  if (command === 'setmatterdiscriminator') {
561
696
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
562
697
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -565,6 +700,7 @@ export class Frontend {
565
700
  res.json({ message: 'Command received' });
566
701
  return;
567
702
  }
703
+ // Handle the command setmatterpasscode from Settings
568
704
  if (command === 'setmatterpasscode') {
569
705
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
570
706
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -573,17 +709,20 @@ export class Frontend {
573
709
  res.json({ message: 'Command received' });
574
710
  return;
575
711
  }
712
+ // Handle the command setmbloglevel from Settings
576
713
  if (command === 'setmblogfile') {
577
714
  this.log.debug('Matterbridge file log:', param);
578
715
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
579
716
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
717
+ // Create the file logger for matterbridge
580
718
  if (param === 'true')
581
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
719
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
582
720
  else
583
721
  AnsiLogger.setGlobalLogfile(undefined);
584
722
  res.json({ message: 'Command received' });
585
723
  return;
586
724
  }
725
+ // Handle the command setmbloglevel from Settings
587
726
  if (command === 'setmjlogfile') {
588
727
  this.log.debug('Matter file log:', param);
589
728
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -610,40 +749,48 @@ export class Frontend {
610
749
  res.json({ message: 'Command received' });
611
750
  return;
612
751
  }
752
+ // Handle the command unregister from Settings
613
753
  if (command === 'unregister') {
614
754
  await this.matterbridge.unregisterAndShutdownProcess();
615
755
  res.json({ message: 'Command received' });
616
756
  return;
617
757
  }
758
+ // Handle the command reset from Settings
618
759
  if (command === 'reset') {
619
760
  await this.matterbridge.shutdownProcessAndReset();
620
761
  res.json({ message: 'Command received' });
621
762
  return;
622
763
  }
764
+ // Handle the command factoryreset from Settings
623
765
  if (command === 'factoryreset') {
624
766
  await this.matterbridge.shutdownProcessAndFactoryReset();
625
767
  res.json({ message: 'Command received' });
626
768
  return;
627
769
  }
770
+ // Handle the command shutdown from Header
628
771
  if (command === 'shutdown') {
629
772
  await this.matterbridge.shutdownProcess();
630
773
  res.json({ message: 'Command received' });
631
774
  return;
632
775
  }
776
+ // Handle the command restart from Header
633
777
  if (command === 'restart') {
634
778
  await this.matterbridge.restartProcess();
635
779
  res.json({ message: 'Command received' });
636
780
  return;
637
781
  }
782
+ // Handle the command update from Header
638
783
  if (command === 'update') {
639
784
  await this.matterbridge.updateProcess();
640
785
  this.wssSendRestartRequired();
641
786
  res.json({ message: 'Command received' });
642
787
  return;
643
788
  }
789
+ // Handle the command saveconfig from Home
644
790
  if (command === 'saveconfig') {
645
791
  param = param.replace(/\*/g, '\\');
646
792
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
793
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
647
794
  if (!this.matterbridge.plugins.has(param)) {
648
795
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
649
796
  }
@@ -657,49 +804,58 @@ export class Frontend {
657
804
  res.json({ message: 'Command received' });
658
805
  return;
659
806
  }
807
+ // Handle the command installplugin from Home
660
808
  if (command === 'installplugin') {
661
809
  param = param.replace(/\*/g, '\\');
662
810
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
663
811
  try {
664
812
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
665
813
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
814
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
666
815
  }
667
816
  catch (error) {
668
817
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
669
818
  }
670
819
  this.wssSendRestartRequired();
671
820
  param = param.split('@')[0];
821
+ // Also add the plugin to matterbridge so no return!
672
822
  if (param === 'matterbridge') {
823
+ // 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
673
824
  res.json({ message: 'Command received' });
674
825
  return;
675
826
  }
676
827
  }
828
+ // Handle the command addplugin from Home
677
829
  if (command === 'addplugin' || command === 'installplugin') {
678
830
  param = param.replace(/\*/g, '\\');
679
831
  const plugin = await this.matterbridge.plugins.add(param);
680
832
  if (plugin) {
681
833
  if (this.matterbridge.bridgeMode === 'childbridge') {
834
+ // 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
835
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
682
836
  this.matterbridge.createDynamicPlugin(plugin, true);
683
837
  }
684
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
838
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
685
839
  }
686
840
  res.json({ message: 'Command received' });
687
841
  this.wssSendRefreshRequired();
688
842
  return;
689
843
  }
844
+ // Handle the command removeplugin from Home
690
845
  if (command === 'removeplugin') {
691
846
  if (!this.matterbridge.plugins.has(param)) {
692
847
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
693
848
  }
694
849
  else {
695
850
  const plugin = this.matterbridge.plugins.get(param);
696
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
851
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
697
852
  await this.matterbridge.plugins.remove(param);
698
853
  }
699
854
  res.json({ message: 'Command received' });
700
855
  this.wssSendRefreshRequired();
701
856
  return;
702
857
  }
858
+ // Handle the command enableplugin from Home
703
859
  if (command === 'enableplugin') {
704
860
  if (!this.matterbridge.plugins.has(param)) {
705
861
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -717,15 +873,17 @@ export class Frontend {
717
873
  plugin.addedDevices = undefined;
718
874
  await this.matterbridge.plugins.enable(param);
719
875
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
876
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
720
877
  this.matterbridge.createDynamicPlugin(plugin, true);
721
878
  }
722
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
879
+ 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
723
880
  }
724
881
  }
725
882
  res.json({ message: 'Command received' });
726
883
  this.wssSendRefreshRequired();
727
884
  return;
728
885
  }
886
+ // Handle the command disableplugin from Home
729
887
  if (command === 'disableplugin') {
730
888
  if (!this.matterbridge.plugins.has(param)) {
731
889
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -733,7 +891,7 @@ export class Frontend {
733
891
  else {
734
892
  const plugin = this.matterbridge.plugins.get(param);
735
893
  if (plugin && plugin.enabled) {
736
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
894
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
737
895
  await this.matterbridge.plugins.disable(param);
738
896
  }
739
897
  }
@@ -742,6 +900,7 @@ export class Frontend {
742
900
  return;
743
901
  }
744
902
  });
903
+ // Fallback for routing (must be the last route)
745
904
  this.expressApp.get('*', (req, res) => {
746
905
  this.log.debug('The frontend sent:', req.url);
747
906
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -750,6 +909,7 @@ export class Frontend {
750
909
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
751
910
  }
752
911
  async stop() {
912
+ // Start the memory check. This will not allow the process to exit but will log the memory usage for 5 minutes.
753
913
  if (hasParameter('memorycheck')) {
754
914
  this.wssSendSnackbarMessage('Memory check started', getIntParameter('memorycheck') ?? 5 * 60 * 1000);
755
915
  await new Promise((resolve) => {
@@ -761,24 +921,29 @@ export class Frontend {
761
921
  }, getIntParameter('memorycheck') ?? 5 * 60 * 1000);
762
922
  });
763
923
  }
924
+ // Close the http server
764
925
  if (this.httpServer) {
765
926
  this.httpServer.close();
766
927
  this.httpServer.removeAllListeners();
767
928
  this.httpServer = undefined;
768
929
  this.log.debug('Frontend http server closed successfully');
769
930
  }
931
+ // Close the https server
770
932
  if (this.httpsServer) {
771
933
  this.httpsServer.close();
772
934
  this.httpsServer.removeAllListeners();
773
935
  this.httpsServer = undefined;
774
936
  this.log.debug('Frontend https server closed successfully');
775
937
  }
938
+ // Remove listeners from the express app
776
939
  if (this.expressApp) {
777
940
  this.expressApp.removeAllListeners();
778
941
  this.expressApp = undefined;
779
942
  this.log.debug('Frontend app closed successfully');
780
943
  }
944
+ // Close the WebSocket server
781
945
  if (this.webSocketServer) {
946
+ // Close all active connections
782
947
  this.webSocketServer.clients.forEach((client) => {
783
948
  if (client.readyState === WebSocket.OPEN) {
784
949
  client.close();
@@ -794,10 +959,12 @@ export class Frontend {
794
959
  });
795
960
  this.webSocketServer = undefined;
796
961
  }
962
+ // Stop the memory dump interval
797
963
  if (hasParameter('memorydump')) {
798
964
  this.stopCpuMemoryDump();
799
965
  }
800
966
  }
967
+ // Function to format bytes to KB, MB, or GB
801
968
  formatMemoryUsage = (bytes) => {
802
969
  if (bytes >= 1024 ** 3) {
803
970
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -809,6 +976,7 @@ export class Frontend {
809
976
  return `${(bytes / 1024).toFixed(2)} KB`;
810
977
  }
811
978
  };
979
+ // Function to format system uptime with only the most significant unit
812
980
  formatOsUpTime = () => {
813
981
  const seconds = os.uptime();
814
982
  if (seconds >= 86400) {
@@ -828,11 +996,12 @@ export class Frontend {
828
996
  getCpuUsage = () => {
829
997
  const currCpus = os.cpus();
830
998
  if (currCpus.length !== this.prevCpus.length) {
831
- this.prevCpus = deepCopy(currCpus);
999
+ this.prevCpus = deepCopy(currCpus); // Reset the previous cpus
832
1000
  this.log.debug(`***Cpu usage reset. Current cpus: ${currCpus.length}. Previous cpus: ${this.prevCpus.length}.`);
833
1001
  return this.lastCpuUsage.toFixed(2);
834
1002
  }
835
1003
  let totalIdle = 0, totalTick = 0;
1004
+ // Get the cpu usage
836
1005
  this.prevCpus.forEach((prevCpu, i) => {
837
1006
  const currCpu = currCpus[i];
838
1007
  const idleDiff = currCpu.times.idle - prevCpu.times.idle;
@@ -853,7 +1022,9 @@ export class Frontend {
853
1022
  clearInterval(this.memoryInterval);
854
1023
  clearTimeout(this.memoryTimeout);
855
1024
  const interval = () => {
1025
+ // Get the cpu usage
856
1026
  const cpuUsage = this.getCpuUsage();
1027
+ // Get the memory usage
857
1028
  const memoryUsageRaw = process.memoryUsage();
858
1029
  this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
859
1030
  const memoryUsage = {
@@ -864,6 +1035,7 @@ export class Frontend {
864
1035
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
865
1036
  };
866
1037
  this.log.debug(`***Cpu usage: ${CYAN}${cpuUsage.padStart(6, ' ')} %${db} - Memory usage: rss ${CYAN}${memoryUsage.rss}${db} heapTotal ${CYAN}${memoryUsage.heapTotal}${db} heapUsed ${CYAN}${memoryUsage.heapUsed}${db} external ${memoryUsage.external} arrayBuffers ${memoryUsage.arrayBuffers}`);
1038
+ // Update the system information
867
1039
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
868
1040
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
869
1041
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime();
@@ -875,7 +1047,7 @@ export class Frontend {
875
1047
  this.wssSendMemoryUpdate(this.matterbridge.systemInformation.freeMemory, this.matterbridge.systemInformation.totalMemory, this.matterbridge.systemInformation.systemUptime, this.matterbridge.systemInformation.rss, this.matterbridge.systemInformation.heapUsed, this.matterbridge.systemInformation.heapTotal);
876
1048
  };
877
1049
  interval();
878
- this.memoryInterval = setInterval(interval, getIntParameter('memoryinterval') ?? 1000);
1050
+ this.memoryInterval = setInterval(interval, getIntParameter('memoryinterval') ?? 1000); // 1 second
879
1051
  this.memoryInterval.unref();
880
1052
  this.memoryTimeout = setTimeout(() => {
881
1053
  this.stopCpuMemoryDump();
@@ -895,12 +1067,18 @@ export class Frontend {
895
1067
  external: this.formatMemoryUsage(memory.external),
896
1068
  arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
897
1069
  };
1070
+ // eslint-disable-next-line no-console
898
1071
  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}`);
899
1072
  }
900
1073
  this.memoryData = [];
901
1074
  this.prevCpus = [];
902
1075
  }
1076
+ /**
1077
+ * Retrieves the api settings data.
1078
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1079
+ */
903
1080
  async getApiSettings() {
1081
+ // Update the system information
904
1082
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
905
1083
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
906
1084
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime();
@@ -908,6 +1086,7 @@ export class Frontend {
908
1086
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
909
1087
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
910
1088
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1089
+ // Update the matterbridge information
911
1090
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
912
1091
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
913
1092
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -926,6 +1105,11 @@ export class Frontend {
926
1105
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
927
1106
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
928
1107
  }
1108
+ /**
1109
+ * Retrieves the cluster text description from a given device.
1110
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1111
+ * @returns {string} The attributes description of the cluster servers in the device.
1112
+ */
929
1113
  getClusterTextFromDevice(device) {
930
1114
  const getAttribute = (device, cluster, attribute) => {
931
1115
  let value = undefined;
@@ -964,6 +1148,7 @@ export class Frontend {
964
1148
  };
965
1149
  let attributes = '';
966
1150
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1151
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
967
1152
  if (typeof attributeValue === 'undefined')
968
1153
  return;
969
1154
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1041,8 +1226,13 @@ export class Frontend {
1041
1226
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1042
1227
  attributes += `${getUserLabel(device)} `;
1043
1228
  });
1229
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1044
1230
  return attributes.trimStart().trimEnd();
1045
1231
  }
1232
+ /**
1233
+ * Retrieves the base registered plugins sanitized for res.json().
1234
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1235
+ */
1046
1236
  getBaseRegisteredPlugins() {
1047
1237
  const baseRegisteredPlugins = [];
1048
1238
  for (const plugin of this.matterbridge.plugins) {
@@ -1073,6 +1263,14 @@ export class Frontend {
1073
1263
  }
1074
1264
  return baseRegisteredPlugins;
1075
1265
  }
1266
+ /**
1267
+ * Handles incoming websocket messages for the Matterbridge.
1268
+ *
1269
+ * @param {Matterbridge} this - The Matterbridge instance.
1270
+ * @param {WebSocket} client - The websocket client that sent the message.
1271
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1272
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1273
+ */
1076
1274
  async wsMessageHandler(client, message) {
1077
1275
  let data;
1078
1276
  try {
@@ -1160,8 +1358,10 @@ export class Frontend {
1160
1358
  else if (data.method === '/api/devices') {
1161
1359
  const devices = [];
1162
1360
  this.matterbridge.devices.forEach(async (device) => {
1361
+ // Filter by pluginName if provided
1163
1362
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1164
1363
  return;
1364
+ // Check if the device has the required properties
1165
1365
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
1166
1366
  return;
1167
1367
  const cluster = this.getClusterTextFromDevice(device);
@@ -1244,6 +1444,7 @@ export class Frontend {
1244
1444
  });
1245
1445
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1246
1446
  deviceTypes = [];
1447
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1247
1448
  const name = childEndpoint.endpoint?.id;
1248
1449
  const clusterServers = childEndpoint.getAllClusterServers();
1249
1450
  clusterServers.forEach((clusterServer) => {
@@ -1326,70 +1527,114 @@ export class Frontend {
1326
1527
  return;
1327
1528
  }
1328
1529
  }
1530
+ /**
1531
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1532
+ *
1533
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1534
+ * @param {string} time - The time string of the message
1535
+ * @param {string} name - The logger name of the message
1536
+ * @param {string} message - The content of the message.
1537
+ */
1329
1538
  wssSendMessage(level, time, name, message) {
1330
1539
  if (!level || !time || !name || !message)
1331
1540
  return;
1541
+ // Remove ANSI escape codes from the message
1542
+ // eslint-disable-next-line no-control-regex
1332
1543
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1544
+ // Remove leading asterisks from the message
1333
1545
  message = message.replace(/^\*+/, '');
1546
+ // Replace all occurrences of \t and \n
1334
1547
  message = message.replace(/[\t\n]/g, '');
1548
+ // Remove non-printable characters
1549
+ // eslint-disable-next-line no-control-regex
1335
1550
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1551
+ // Replace all occurrences of \" with "
1336
1552
  message = message.replace(/\\"/g, '"');
1553
+ // Define the maximum allowed length for continuous characters without a space
1337
1554
  const maxContinuousLength = 100;
1338
1555
  const keepStartLength = 20;
1339
1556
  const keepEndLength = 20;
1557
+ // Split the message into words
1340
1558
  message = message
1341
1559
  .split(' ')
1342
1560
  .map((word) => {
1561
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1343
1562
  if (word.length > maxContinuousLength) {
1344
1563
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1345
1564
  }
1346
1565
  return word;
1347
1566
  })
1348
1567
  .join(' ');
1568
+ // Send the message to all connected clients
1349
1569
  this.webSocketServer?.clients.forEach((client) => {
1350
1570
  if (client.readyState === WebSocket.OPEN) {
1351
1571
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1352
1572
  }
1353
1573
  });
1354
1574
  }
1575
+ /**
1576
+ * Sends a need to refresh WebSocket message to all connected clients.
1577
+ *
1578
+ */
1355
1579
  wssSendRefreshRequired() {
1356
1580
  this.log.debug('Sending a refresh required message to all connected clients');
1357
1581
  this.matterbridge.matterbridgeInformation.refreshRequired = true;
1582
+ // Send the message to all connected clients
1358
1583
  this.webSocketServer?.clients.forEach((client) => {
1359
1584
  if (client.readyState === WebSocket.OPEN) {
1360
1585
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1361
1586
  }
1362
1587
  });
1363
1588
  }
1589
+ /**
1590
+ * Sends a need to restart WebSocket message to all connected clients.
1591
+ *
1592
+ */
1364
1593
  wssSendRestartRequired() {
1365
1594
  this.log.debug('Sending a restart required message to all connected clients');
1366
1595
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1596
+ // Send the message to all connected clients
1367
1597
  this.webSocketServer?.clients.forEach((client) => {
1368
1598
  if (client.readyState === WebSocket.OPEN) {
1369
1599
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1370
1600
  }
1371
1601
  });
1372
1602
  }
1603
+ /**
1604
+ * Sends a memory update message to all connected clients.
1605
+ *
1606
+ */
1373
1607
  wssSendCpuUpdate(cpuUsed) {
1374
1608
  this.log.debug('Sending a memory update message to all connected clients');
1375
1609
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1610
+ // Send the message to all connected clients
1376
1611
  this.webSocketServer?.clients.forEach((client) => {
1377
1612
  if (client.readyState === WebSocket.OPEN) {
1378
1613
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsed } }));
1379
1614
  }
1380
1615
  });
1381
1616
  }
1617
+ /**
1618
+ * Sends a cpu update message to all connected clients.
1619
+ *
1620
+ */
1382
1621
  wssSendMemoryUpdate(freeMemory, totalMemory, systemUptime, rss, heapUsed, heapTotal) {
1383
1622
  this.log.debug('Sending a cpu update message to all connected clients');
1384
1623
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1624
+ // Send the message to all connected clients
1385
1625
  this.webSocketServer?.clients.forEach((client) => {
1386
1626
  if (client.readyState === WebSocket.OPEN) {
1387
1627
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { freeMemory, totalMemory, systemUptime, rss, heapUsed, heapTotal } }));
1388
1628
  }
1389
1629
  });
1390
1630
  }
1631
+ /**
1632
+ * Sends a cpu update message to all connected clients.
1633
+ *
1634
+ */
1391
1635
  wssSendSnackbarMessage(message, timeout = 5) {
1392
1636
  this.log.debug('Sending a snackbar message to all connected clients');
1637
+ // Send the message to all connected clients
1393
1638
  this.webSocketServer?.clients.forEach((client) => {
1394
1639
  if (client.readyState === WebSocket.OPEN) {
1395
1640
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
@@ -1397,3 +1642,4 @@ export class Frontend {
1397
1642
  });
1398
1643
  }
1399
1644
  }
1645
+ //# sourceMappingURL=frontend.js.map