matterbridge 2.1.5-dev.8 → 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 +268 -28
  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 +748 -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 +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 +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,16 +200,18 @@ 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
- let callbackLogLevel = "notice";
138
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
139
- callbackLogLevel = "info";
140
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
141
- callbackLogLevel = "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 */;
142
215
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
143
216
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
144
217
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -172,9 +245,11 @@ export class Frontend {
172
245
  this.webSocketServer.on('error', (ws, error) => {
173
246
  this.log.error(`WebSocketServer error: ${error}`);
174
247
  });
248
+ // Start the memory dump interval
175
249
  if (hasParameter('memorydump')) {
176
250
  this.startCpuMemoryDump();
177
251
  }
252
+ // Endpoint to validate login code
178
253
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
179
254
  const { password } = req.body;
180
255
  this.log.debug('The frontend sent /api/login', password);
@@ -193,23 +268,27 @@ export class Frontend {
193
268
  this.log.warn('/api/login error wrong password');
194
269
  res.json({ valid: false });
195
270
  }
271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
196
272
  }
197
273
  catch (error) {
198
274
  this.log.error('/api/login error getting password');
199
275
  res.json({ valid: false });
200
276
  }
201
277
  });
278
+ // Endpoint to provide health check
202
279
  this.expressApp.get('/health', (req, res) => {
203
280
  this.log.debug('Express received /health');
204
281
  const healthStatus = {
205
- status: 'ok',
206
- uptime: process.uptime(),
207
- 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
208
285
  };
209
286
  res.status(200).json(healthStatus);
210
287
  });
288
+ // Endpoint to provide memory usage details
211
289
  this.expressApp.get('/memory', async (req, res) => {
212
290
  this.log.debug('Express received /memory');
291
+ // Memory usage from process
213
292
  const memoryUsageRaw = process.memoryUsage();
214
293
  const memoryUsage = {
215
294
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -218,10 +297,13 @@ export class Frontend {
218
297
  external: this.formatMemoryUsage(memoryUsageRaw.external),
219
298
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
220
299
  };
300
+ // V8 heap statistics
221
301
  const { default: v8 } = await import('node:v8');
222
302
  const heapStatsRaw = v8.getHeapStatistics();
223
303
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
304
+ // Format heapStats
224
305
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
306
+ // Format heapSpaces
225
307
  const heapSpaces = heapSpacesRaw.map((space) => ({
226
308
  ...space,
227
309
  space_size: this.formatMemoryUsage(space.space_size),
@@ -231,6 +313,23 @@ export class Frontend {
231
313
  }));
232
314
  const { default: module } = await import('module');
233
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
+ */
234
333
  const memoryReport = {
235
334
  memoryUsage,
236
335
  heapStats,
@@ -239,6 +338,7 @@ export class Frontend {
239
338
  };
240
339
  res.status(200).json(memoryReport);
241
340
  });
341
+ // Endpoint to start advertising the server node
242
342
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
243
343
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
244
344
  if (pairingCodes) {
@@ -249,19 +349,24 @@ export class Frontend {
249
349
  res.status(500).json({ error: 'Failed to generate pairing codes' });
250
350
  }
251
351
  });
352
+ // Endpoint to provide settings
252
353
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
253
354
  this.log.debug('The frontend sent /api/settings');
254
355
  res.json(await this.getApiSettings());
255
356
  });
357
+ // Endpoint to provide plugins
256
358
  this.expressApp.get('/api/plugins', async (req, res) => {
257
359
  this.log.debug('The frontend sent /api/plugins');
258
360
  const response = this.getBaseRegisteredPlugins();
361
+ // this.log.debug('Response:', debugStringify(response));
259
362
  res.json(response);
260
363
  });
364
+ // Endpoint to provide devices
261
365
  this.expressApp.get('/api/devices', (req, res) => {
262
366
  this.log.debug('The frontend sent /api/devices');
263
367
  const devices = [];
264
368
  this.matterbridge.devices.forEach(async (device) => {
369
+ // Check if the device has the required properties
265
370
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
266
371
  return;
267
372
  const cluster = this.getClusterTextFromDevice(device);
@@ -277,8 +382,10 @@ export class Frontend {
277
382
  cluster: cluster,
278
383
  });
279
384
  });
385
+ // this.log.debug('Response:', debugStringify(data));
280
386
  res.json(devices);
281
387
  });
388
+ // Endpoint to provide the cluster servers of the devices
282
389
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
283
390
  const selectedPluginName = req.params.selectedPluginName;
284
391
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -351,6 +458,7 @@ export class Frontend {
351
458
  });
352
459
  res.json(data);
353
460
  });
461
+ // Endpoint to view the log
354
462
  this.expressApp.get('/api/view-log', async (req, res) => {
355
463
  this.log.debug('The frontend sent /api/log');
356
464
  try {
@@ -363,10 +471,12 @@ export class Frontend {
363
471
  res.status(500).send('Error reading log file');
364
472
  }
365
473
  });
474
+ // Endpoint to download the matterbridge log
366
475
  this.expressApp.get('/api/download-mblog', async (req, res) => {
367
476
  this.log.debug('The frontend sent /api/download-mblog');
368
477
  try {
369
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
370
480
  }
371
481
  catch (error) {
372
482
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -378,10 +488,12 @@ export class Frontend {
378
488
  }
379
489
  });
380
490
  });
491
+ // Endpoint to download the matter log
381
492
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
382
493
  this.log.debug('The frontend sent /api/download-mjlog');
383
494
  try {
384
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
385
497
  }
386
498
  catch (error) {
387
499
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -393,6 +505,7 @@ export class Frontend {
393
505
  }
394
506
  });
395
507
  });
508
+ // Endpoint to download the matter storage file
396
509
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
397
510
  this.log.debug('The frontend sent /api/download-mjstorage');
398
511
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -403,6 +516,7 @@ export class Frontend {
403
516
  }
404
517
  });
405
518
  });
519
+ // Endpoint to download the matterbridge storage directory
406
520
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
407
521
  this.log.debug('The frontend sent /api/download-mbstorage');
408
522
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -413,6 +527,7 @@ export class Frontend {
413
527
  }
414
528
  });
415
529
  });
530
+ // Endpoint to download the matterbridge plugin directory
416
531
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
417
532
  this.log.debug('The frontend sent /api/download-pluginstorage');
418
533
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -423,9 +538,11 @@ export class Frontend {
423
538
  }
424
539
  });
425
540
  });
541
+ // Endpoint to download the matterbridge plugin config files
426
542
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
427
543
  this.log.debug('The frontend sent /api/download-pluginconfig');
428
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')));
429
546
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
430
547
  if (error) {
431
548
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -433,6 +550,7 @@ export class Frontend {
433
550
  }
434
551
  });
435
552
  });
553
+ // Endpoint to download the matterbridge plugin config files
436
554
  this.expressApp.get('/api/download-backup', async (req, res) => {
437
555
  this.log.debug('The frontend sent /api/download-backup');
438
556
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -442,6 +560,7 @@ export class Frontend {
442
560
  }
443
561
  });
444
562
  });
563
+ // Endpoint to receive commands
445
564
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
446
565
  const command = req.params.command;
447
566
  let param = req.params.param;
@@ -451,13 +570,15 @@ export class Frontend {
451
570
  return;
452
571
  }
453
572
  this.log.debug(`Received frontend command: ${command}:${param}`);
573
+ // Handle the command setpassword from Settings
454
574
  if (command === 'setpassword') {
455
- const password = param.slice(1, -1);
575
+ const password = param.slice(1, -1); // Remove the first and last characters
456
576
  this.log.debug('setpassword', param, password);
457
577
  await this.matterbridge.nodeContext?.set('password', password);
458
578
  res.json({ message: 'Command received' });
459
579
  return;
460
580
  }
581
+ // Handle the command setbridgemode from Settings
461
582
  if (command === 'setbridgemode') {
462
583
  this.log.debug(`setbridgemode: ${param}`);
463
584
  this.wssSendRestartRequired();
@@ -465,6 +586,7 @@ export class Frontend {
465
586
  res.json({ message: 'Command received' });
466
587
  return;
467
588
  }
589
+ // Handle the command backup from Settings
468
590
  if (command === 'backup') {
469
591
  this.log.notice(`Prepairing the backup...`);
470
592
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -473,25 +595,26 @@ export class Frontend {
473
595
  res.json({ message: 'Command received' });
474
596
  return;
475
597
  }
598
+ // Handle the command setmbloglevel from Settings
476
599
  if (command === 'setmbloglevel') {
477
600
  this.log.debug('Matterbridge log level:', param);
478
601
  if (param === 'Debug') {
479
- this.log.logLevel = "debug";
602
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
480
603
  }
481
604
  else if (param === 'Info') {
482
- this.log.logLevel = "info";
605
+ this.log.logLevel = "info" /* LogLevel.INFO */;
483
606
  }
484
607
  else if (param === 'Notice') {
485
- this.log.logLevel = "notice";
608
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
486
609
  }
487
610
  else if (param === 'Warn') {
488
- this.log.logLevel = "warn";
611
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
489
612
  }
490
613
  else if (param === 'Error') {
491
- this.log.logLevel = "error";
614
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
492
615
  }
493
616
  else if (param === 'Fatal') {
494
- this.log.logLevel = "fatal";
617
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
495
618
  }
496
619
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
497
620
  this.matterbridge.log.logLevel = this.log.logLevel;
@@ -501,12 +624,13 @@ export class Frontend {
501
624
  for (const plugin of this.matterbridge.plugins) {
502
625
  if (!plugin.platform || !plugin.platform.config)
503
626
  continue;
504
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
505
- 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);
506
629
  }
507
630
  res.json({ message: 'Command received' });
508
631
  return;
509
632
  }
633
+ // Handle the command setmbloglevel from Settings
510
634
  if (command === 'setmjloglevel') {
511
635
  this.log.debug('Matter.js log level:', param);
512
636
  if (param === 'Debug') {
@@ -531,30 +655,34 @@ export class Frontend {
531
655
  res.json({ message: 'Command received' });
532
656
  return;
533
657
  }
658
+ // Handle the command setmdnsinterface from Settings
534
659
  if (command === 'setmdnsinterface') {
535
- param = param.slice(1, -1);
660
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
536
661
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
537
662
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
538
663
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
539
664
  res.json({ message: 'Command received' });
540
665
  return;
541
666
  }
667
+ // Handle the command setipv4address from Settings
542
668
  if (command === 'setipv4address') {
543
- param = param.slice(1, -1);
669
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
544
670
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
545
671
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
546
672
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
547
673
  res.json({ message: 'Command received' });
548
674
  return;
549
675
  }
676
+ // Handle the command setipv6address from Settings
550
677
  if (command === 'setipv6address') {
551
- param = param.slice(1, -1);
678
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
552
679
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
553
680
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
554
681
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
555
682
  res.json({ message: 'Command received' });
556
683
  return;
557
684
  }
685
+ // Handle the command setmatterport from Settings
558
686
  if (command === 'setmatterport') {
559
687
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
560
688
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -563,6 +691,7 @@ export class Frontend {
563
691
  res.json({ message: 'Command received' });
564
692
  return;
565
693
  }
694
+ // Handle the command setmatterdiscriminator from Settings
566
695
  if (command === 'setmatterdiscriminator') {
567
696
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
568
697
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -571,6 +700,7 @@ export class Frontend {
571
700
  res.json({ message: 'Command received' });
572
701
  return;
573
702
  }
703
+ // Handle the command setmatterpasscode from Settings
574
704
  if (command === 'setmatterpasscode') {
575
705
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
576
706
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -579,17 +709,20 @@ export class Frontend {
579
709
  res.json({ message: 'Command received' });
580
710
  return;
581
711
  }
712
+ // Handle the command setmbloglevel from Settings
582
713
  if (command === 'setmblogfile') {
583
714
  this.log.debug('Matterbridge file log:', param);
584
715
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
585
716
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
717
+ // Create the file logger for matterbridge
586
718
  if (param === 'true')
587
- 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);
588
720
  else
589
721
  AnsiLogger.setGlobalLogfile(undefined);
590
722
  res.json({ message: 'Command received' });
591
723
  return;
592
724
  }
725
+ // Handle the command setmbloglevel from Settings
593
726
  if (command === 'setmjlogfile') {
594
727
  this.log.debug('Matter file log:', param);
595
728
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -616,40 +749,48 @@ export class Frontend {
616
749
  res.json({ message: 'Command received' });
617
750
  return;
618
751
  }
752
+ // Handle the command unregister from Settings
619
753
  if (command === 'unregister') {
620
754
  await this.matterbridge.unregisterAndShutdownProcess();
621
755
  res.json({ message: 'Command received' });
622
756
  return;
623
757
  }
758
+ // Handle the command reset from Settings
624
759
  if (command === 'reset') {
625
760
  await this.matterbridge.shutdownProcessAndReset();
626
761
  res.json({ message: 'Command received' });
627
762
  return;
628
763
  }
764
+ // Handle the command factoryreset from Settings
629
765
  if (command === 'factoryreset') {
630
766
  await this.matterbridge.shutdownProcessAndFactoryReset();
631
767
  res.json({ message: 'Command received' });
632
768
  return;
633
769
  }
770
+ // Handle the command shutdown from Header
634
771
  if (command === 'shutdown') {
635
772
  await this.matterbridge.shutdownProcess();
636
773
  res.json({ message: 'Command received' });
637
774
  return;
638
775
  }
776
+ // Handle the command restart from Header
639
777
  if (command === 'restart') {
640
778
  await this.matterbridge.restartProcess();
641
779
  res.json({ message: 'Command received' });
642
780
  return;
643
781
  }
782
+ // Handle the command update from Header
644
783
  if (command === 'update') {
645
784
  await this.matterbridge.updateProcess();
646
785
  this.wssSendRestartRequired();
647
786
  res.json({ message: 'Command received' });
648
787
  return;
649
788
  }
789
+ // Handle the command saveconfig from Home
650
790
  if (command === 'saveconfig') {
651
791
  param = param.replace(/\*/g, '\\');
652
792
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
793
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
653
794
  if (!this.matterbridge.plugins.has(param)) {
654
795
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
655
796
  }
@@ -663,49 +804,58 @@ export class Frontend {
663
804
  res.json({ message: 'Command received' });
664
805
  return;
665
806
  }
807
+ // Handle the command installplugin from Home
666
808
  if (command === 'installplugin') {
667
809
  param = param.replace(/\*/g, '\\');
668
810
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
669
811
  try {
670
812
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
671
813
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
814
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
672
815
  }
673
816
  catch (error) {
674
817
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
675
818
  }
676
819
  this.wssSendRestartRequired();
677
820
  param = param.split('@')[0];
821
+ // Also add the plugin to matterbridge so no return!
678
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
679
824
  res.json({ message: 'Command received' });
680
825
  return;
681
826
  }
682
827
  }
828
+ // Handle the command addplugin from Home
683
829
  if (command === 'addplugin' || command === 'installplugin') {
684
830
  param = param.replace(/\*/g, '\\');
685
831
  const plugin = await this.matterbridge.plugins.add(param);
686
832
  if (plugin) {
687
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
688
836
  this.matterbridge.createDynamicPlugin(plugin, true);
689
837
  }
690
- 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
691
839
  }
692
840
  res.json({ message: 'Command received' });
693
841
  this.wssSendRefreshRequired();
694
842
  return;
695
843
  }
844
+ // Handle the command removeplugin from Home
696
845
  if (command === 'removeplugin') {
697
846
  if (!this.matterbridge.plugins.has(param)) {
698
847
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
699
848
  }
700
849
  else {
701
850
  const plugin = this.matterbridge.plugins.get(param);
702
- 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
703
852
  await this.matterbridge.plugins.remove(param);
704
853
  }
705
854
  res.json({ message: 'Command received' });
706
855
  this.wssSendRefreshRequired();
707
856
  return;
708
857
  }
858
+ // Handle the command enableplugin from Home
709
859
  if (command === 'enableplugin') {
710
860
  if (!this.matterbridge.plugins.has(param)) {
711
861
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -723,15 +873,17 @@ export class Frontend {
723
873
  plugin.addedDevices = undefined;
724
874
  await this.matterbridge.plugins.enable(param);
725
875
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
876
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
726
877
  this.matterbridge.createDynamicPlugin(plugin, true);
727
878
  }
728
- 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
729
880
  }
730
881
  }
731
882
  res.json({ message: 'Command received' });
732
883
  this.wssSendRefreshRequired();
733
884
  return;
734
885
  }
886
+ // Handle the command disableplugin from Home
735
887
  if (command === 'disableplugin') {
736
888
  if (!this.matterbridge.plugins.has(param)) {
737
889
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -739,7 +891,7 @@ export class Frontend {
739
891
  else {
740
892
  const plugin = this.matterbridge.plugins.get(param);
741
893
  if (plugin && plugin.enabled) {
742
- 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
743
895
  await this.matterbridge.plugins.disable(param);
744
896
  }
745
897
  }
@@ -748,6 +900,7 @@ export class Frontend {
748
900
  return;
749
901
  }
750
902
  });
903
+ // Fallback for routing (must be the last route)
751
904
  this.expressApp.get('*', (req, res) => {
752
905
  this.log.debug('The frontend sent:', req.url);
753
906
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -756,6 +909,7 @@ export class Frontend {
756
909
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
757
910
  }
758
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.
759
913
  if (hasParameter('memorycheck')) {
760
914
  this.wssSendSnackbarMessage('Memory check started', getIntParameter('memorycheck') ?? 5 * 60 * 1000);
761
915
  await new Promise((resolve) => {
@@ -767,24 +921,29 @@ export class Frontend {
767
921
  }, getIntParameter('memorycheck') ?? 5 * 60 * 1000);
768
922
  });
769
923
  }
924
+ // Close the http server
770
925
  if (this.httpServer) {
771
926
  this.httpServer.close();
772
927
  this.httpServer.removeAllListeners();
773
928
  this.httpServer = undefined;
774
929
  this.log.debug('Frontend http server closed successfully');
775
930
  }
931
+ // Close the https server
776
932
  if (this.httpsServer) {
777
933
  this.httpsServer.close();
778
934
  this.httpsServer.removeAllListeners();
779
935
  this.httpsServer = undefined;
780
936
  this.log.debug('Frontend https server closed successfully');
781
937
  }
938
+ // Remove listeners from the express app
782
939
  if (this.expressApp) {
783
940
  this.expressApp.removeAllListeners();
784
941
  this.expressApp = undefined;
785
942
  this.log.debug('Frontend app closed successfully');
786
943
  }
944
+ // Close the WebSocket server
787
945
  if (this.webSocketServer) {
946
+ // Close all active connections
788
947
  this.webSocketServer.clients.forEach((client) => {
789
948
  if (client.readyState === WebSocket.OPEN) {
790
949
  client.close();
@@ -800,10 +959,12 @@ export class Frontend {
800
959
  });
801
960
  this.webSocketServer = undefined;
802
961
  }
962
+ // Stop the memory dump interval
803
963
  if (hasParameter('memorydump')) {
804
964
  this.stopCpuMemoryDump();
805
965
  }
806
966
  }
967
+ // Function to format bytes to KB, MB, or GB
807
968
  formatMemoryUsage = (bytes) => {
808
969
  if (bytes >= 1024 ** 3) {
809
970
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -815,6 +976,7 @@ export class Frontend {
815
976
  return `${(bytes / 1024).toFixed(2)} KB`;
816
977
  }
817
978
  };
979
+ // Function to format system uptime with only the most significant unit
818
980
  formatOsUpTime = () => {
819
981
  const seconds = os.uptime();
820
982
  if (seconds >= 86400) {
@@ -834,11 +996,12 @@ export class Frontend {
834
996
  getCpuUsage = () => {
835
997
  const currCpus = os.cpus();
836
998
  if (currCpus.length !== this.prevCpus.length) {
837
- this.prevCpus = deepCopy(currCpus);
999
+ this.prevCpus = deepCopy(currCpus); // Reset the previous cpus
838
1000
  this.log.debug(`***Cpu usage reset. Current cpus: ${currCpus.length}. Previous cpus: ${this.prevCpus.length}.`);
839
1001
  return this.lastCpuUsage.toFixed(2);
840
1002
  }
841
1003
  let totalIdle = 0, totalTick = 0;
1004
+ // Get the cpu usage
842
1005
  this.prevCpus.forEach((prevCpu, i) => {
843
1006
  const currCpu = currCpus[i];
844
1007
  const idleDiff = currCpu.times.idle - prevCpu.times.idle;
@@ -859,7 +1022,9 @@ export class Frontend {
859
1022
  clearInterval(this.memoryInterval);
860
1023
  clearTimeout(this.memoryTimeout);
861
1024
  const interval = () => {
1025
+ // Get the cpu usage
862
1026
  const cpuUsage = this.getCpuUsage();
1027
+ // Get the memory usage
863
1028
  const memoryUsageRaw = process.memoryUsage();
864
1029
  this.memoryData.push({ ...memoryUsageRaw, cpu: cpuUsage });
865
1030
  const memoryUsage = {
@@ -870,6 +1035,7 @@ export class Frontend {
870
1035
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
871
1036
  };
872
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
873
1039
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
874
1040
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
875
1041
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime();
@@ -881,7 +1047,7 @@ export class Frontend {
881
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);
882
1048
  };
883
1049
  interval();
884
- this.memoryInterval = setInterval(interval, getIntParameter('memoryinterval') ?? 1000);
1050
+ this.memoryInterval = setInterval(interval, getIntParameter('memoryinterval') ?? 1000); // 1 second
885
1051
  this.memoryInterval.unref();
886
1052
  this.memoryTimeout = setTimeout(() => {
887
1053
  this.stopCpuMemoryDump();
@@ -901,12 +1067,18 @@ export class Frontend {
901
1067
  external: this.formatMemoryUsage(memory.external),
902
1068
  arrayBuffers: this.formatMemoryUsage(memory.arrayBuffers),
903
1069
  };
1070
+ // eslint-disable-next-line no-console
904
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}`);
905
1072
  }
906
1073
  this.memoryData = [];
907
1074
  this.prevCpus = [];
908
1075
  }
1076
+ /**
1077
+ * Retrieves the api settings data.
1078
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1079
+ */
909
1080
  async getApiSettings() {
1081
+ // Update the system information
910
1082
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
911
1083
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
912
1084
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime();
@@ -914,6 +1086,7 @@ export class Frontend {
914
1086
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
915
1087
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
916
1088
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1089
+ // Update the matterbridge information
917
1090
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
918
1091
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
919
1092
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -932,6 +1105,11 @@ export class Frontend {
932
1105
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
933
1106
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
934
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
+ */
935
1113
  getClusterTextFromDevice(device) {
936
1114
  const getAttribute = (device, cluster, attribute) => {
937
1115
  let value = undefined;
@@ -970,6 +1148,7 @@ export class Frontend {
970
1148
  };
971
1149
  let attributes = '';
972
1150
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1151
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
973
1152
  if (typeof attributeValue === 'undefined')
974
1153
  return;
975
1154
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1047,8 +1226,13 @@ export class Frontend {
1047
1226
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1048
1227
  attributes += `${getUserLabel(device)} `;
1049
1228
  });
1229
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1050
1230
  return attributes.trimStart().trimEnd();
1051
1231
  }
1232
+ /**
1233
+ * Retrieves the base registered plugins sanitized for res.json().
1234
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1235
+ */
1052
1236
  getBaseRegisteredPlugins() {
1053
1237
  const baseRegisteredPlugins = [];
1054
1238
  for (const plugin of this.matterbridge.plugins) {
@@ -1079,6 +1263,14 @@ export class Frontend {
1079
1263
  }
1080
1264
  return baseRegisteredPlugins;
1081
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
+ */
1082
1274
  async wsMessageHandler(client, message) {
1083
1275
  let data;
1084
1276
  try {
@@ -1166,8 +1358,10 @@ export class Frontend {
1166
1358
  else if (data.method === '/api/devices') {
1167
1359
  const devices = [];
1168
1360
  this.matterbridge.devices.forEach(async (device) => {
1361
+ // Filter by pluginName if provided
1169
1362
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1170
1363
  return;
1364
+ // Check if the device has the required properties
1171
1365
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
1172
1366
  return;
1173
1367
  const cluster = this.getClusterTextFromDevice(device);
@@ -1250,6 +1444,7 @@ export class Frontend {
1250
1444
  });
1251
1445
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1252
1446
  deviceTypes = [];
1447
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1253
1448
  const name = childEndpoint.endpoint?.id;
1254
1449
  const clusterServers = childEndpoint.getAllClusterServers();
1255
1450
  clusterServers.forEach((clusterServer) => {
@@ -1332,70 +1527,114 @@ export class Frontend {
1332
1527
  return;
1333
1528
  }
1334
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
+ */
1335
1538
  wssSendMessage(level, time, name, message) {
1336
1539
  if (!level || !time || !name || !message)
1337
1540
  return;
1541
+ // Remove ANSI escape codes from the message
1542
+ // eslint-disable-next-line no-control-regex
1338
1543
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1544
+ // Remove leading asterisks from the message
1339
1545
  message = message.replace(/^\*+/, '');
1546
+ // Replace all occurrences of \t and \n
1340
1547
  message = message.replace(/[\t\n]/g, '');
1548
+ // Remove non-printable characters
1549
+ // eslint-disable-next-line no-control-regex
1341
1550
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1551
+ // Replace all occurrences of \" with "
1342
1552
  message = message.replace(/\\"/g, '"');
1553
+ // Define the maximum allowed length for continuous characters without a space
1343
1554
  const maxContinuousLength = 100;
1344
1555
  const keepStartLength = 20;
1345
1556
  const keepEndLength = 20;
1557
+ // Split the message into words
1346
1558
  message = message
1347
1559
  .split(' ')
1348
1560
  .map((word) => {
1561
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1349
1562
  if (word.length > maxContinuousLength) {
1350
1563
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1351
1564
  }
1352
1565
  return word;
1353
1566
  })
1354
1567
  .join(' ');
1568
+ // Send the message to all connected clients
1355
1569
  this.webSocketServer?.clients.forEach((client) => {
1356
1570
  if (client.readyState === WebSocket.OPEN) {
1357
1571
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1358
1572
  }
1359
1573
  });
1360
1574
  }
1575
+ /**
1576
+ * Sends a need to refresh WebSocket message to all connected clients.
1577
+ *
1578
+ */
1361
1579
  wssSendRefreshRequired() {
1362
1580
  this.log.debug('Sending a refresh required message to all connected clients');
1363
1581
  this.matterbridge.matterbridgeInformation.refreshRequired = true;
1582
+ // Send the message to all connected clients
1364
1583
  this.webSocketServer?.clients.forEach((client) => {
1365
1584
  if (client.readyState === WebSocket.OPEN) {
1366
1585
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1367
1586
  }
1368
1587
  });
1369
1588
  }
1589
+ /**
1590
+ * Sends a need to restart WebSocket message to all connected clients.
1591
+ *
1592
+ */
1370
1593
  wssSendRestartRequired() {
1371
1594
  this.log.debug('Sending a restart required message to all connected clients');
1372
1595
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1596
+ // Send the message to all connected clients
1373
1597
  this.webSocketServer?.clients.forEach((client) => {
1374
1598
  if (client.readyState === WebSocket.OPEN) {
1375
1599
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1376
1600
  }
1377
1601
  });
1378
1602
  }
1603
+ /**
1604
+ * Sends a memory update message to all connected clients.
1605
+ *
1606
+ */
1379
1607
  wssSendCpuUpdate(cpuUsed) {
1380
1608
  this.log.debug('Sending a memory update message to all connected clients');
1381
1609
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1610
+ // Send the message to all connected clients
1382
1611
  this.webSocketServer?.clients.forEach((client) => {
1383
1612
  if (client.readyState === WebSocket.OPEN) {
1384
1613
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsed } }));
1385
1614
  }
1386
1615
  });
1387
1616
  }
1617
+ /**
1618
+ * Sends a cpu update message to all connected clients.
1619
+ *
1620
+ */
1388
1621
  wssSendMemoryUpdate(freeMemory, totalMemory, systemUptime, rss, heapUsed, heapTotal) {
1389
1622
  this.log.debug('Sending a cpu update message to all connected clients');
1390
1623
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1624
+ // Send the message to all connected clients
1391
1625
  this.webSocketServer?.clients.forEach((client) => {
1392
1626
  if (client.readyState === WebSocket.OPEN) {
1393
1627
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { freeMemory, totalMemory, systemUptime, rss, heapUsed, heapTotal } }));
1394
1628
  }
1395
1629
  });
1396
1630
  }
1631
+ /**
1632
+ * Sends a cpu update message to all connected clients.
1633
+ *
1634
+ */
1397
1635
  wssSendSnackbarMessage(message, timeout = 5) {
1398
1636
  this.log.debug('Sending a snackbar message to all connected clients');
1637
+ // Send the message to all connected clients
1399
1638
  this.webSocketServer?.clients.forEach((client) => {
1400
1639
  if (client.readyState === WebSocket.OPEN) {
1401
1640
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
@@ -1403,3 +1642,4 @@ export class Frontend {
1403
1642
  });
1404
1643
  }
1405
1644
  }
1645
+ //# sourceMappingURL=frontend.js.map