matterbridge 3.0.3-dev-20250520-e6e7257 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/cli.d.ts +29 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +37 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cluster/export.d.ts +2 -0
  6. package/dist/cluster/export.d.ts.map +1 -0
  7. package/dist/cluster/export.js +2 -0
  8. package/dist/cluster/export.js.map +1 -0
  9. package/dist/defaultConfigSchema.d.ts +27 -0
  10. package/dist/defaultConfigSchema.d.ts.map +1 -0
  11. package/dist/defaultConfigSchema.js +23 -0
  12. package/dist/defaultConfigSchema.js.map +1 -0
  13. package/dist/deviceManager.d.ts +114 -0
  14. package/dist/deviceManager.d.ts.map +1 -0
  15. package/dist/deviceManager.js +94 -1
  16. package/dist/deviceManager.js.map +1 -0
  17. package/dist/frontend.d.ts +241 -0
  18. package/dist/frontend.d.ts.map +1 -0
  19. package/dist/frontend.js +333 -15
  20. package/dist/frontend.js.map +1 -0
  21. package/dist/helpers.d.ts +47 -0
  22. package/dist/helpers.d.ts.map +1 -0
  23. package/dist/helpers.js +50 -0
  24. package/dist/helpers.js.map +1 -0
  25. package/dist/index.d.ts +35 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +27 -1
  28. package/dist/index.js.map +1 -0
  29. package/dist/logger/export.d.ts +2 -0
  30. package/dist/logger/export.d.ts.map +1 -0
  31. package/dist/logger/export.js +1 -0
  32. package/dist/logger/export.js.map +1 -0
  33. package/dist/matter/behaviors.d.ts +2 -0
  34. package/dist/matter/behaviors.d.ts.map +1 -0
  35. package/dist/matter/behaviors.js +2 -0
  36. package/dist/matter/behaviors.js.map +1 -0
  37. package/dist/matter/clusters.d.ts +2 -0
  38. package/dist/matter/clusters.d.ts.map +1 -0
  39. package/dist/matter/clusters.js +2 -0
  40. package/dist/matter/clusters.js.map +1 -0
  41. package/dist/matter/devices.d.ts +2 -0
  42. package/dist/matter/devices.d.ts.map +1 -0
  43. package/dist/matter/devices.js +2 -0
  44. package/dist/matter/devices.js.map +1 -0
  45. package/dist/matter/endpoints.d.ts +2 -0
  46. package/dist/matter/endpoints.d.ts.map +1 -0
  47. package/dist/matter/endpoints.js +2 -0
  48. package/dist/matter/endpoints.js.map +1 -0
  49. package/dist/matter/export.d.ts +5 -0
  50. package/dist/matter/export.d.ts.map +1 -0
  51. package/dist/matter/export.js +2 -0
  52. package/dist/matter/export.js.map +1 -0
  53. package/dist/matter/types.d.ts +3 -0
  54. package/dist/matter/types.d.ts.map +1 -0
  55. package/dist/matter/types.js +2 -0
  56. package/dist/matter/types.js.map +1 -0
  57. package/dist/matterbridge.d.ts +445 -0
  58. package/dist/matterbridge.d.ts.map +1 -0
  59. package/dist/matterbridge.js +747 -47
  60. package/dist/matterbridge.js.map +1 -0
  61. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  62. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  63. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  64. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  65. package/dist/matterbridgeBehaviors.d.ts +1201 -0
  66. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  67. package/dist/matterbridgeBehaviors.js +60 -4
  68. package/dist/matterbridgeBehaviors.js.map +1 -0
  69. package/dist/matterbridgeDeviceTypes.d.ts +629 -0
  70. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  71. package/dist/matterbridgeDeviceTypes.js +563 -15
  72. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  73. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  74. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  75. package/dist/matterbridgeDynamicPlatform.js +34 -0
  76. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  77. package/dist/matterbridgeEndpoint.d.ts +967 -0
  78. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  79. package/dist/matterbridgeEndpoint.js +807 -11
  80. package/dist/matterbridgeEndpoint.js.map +1 -0
  81. package/dist/matterbridgeEndpointHelpers.d.ts +2728 -0
  82. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  83. package/dist/matterbridgeEndpointHelpers.js +149 -10
  84. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  85. package/dist/matterbridgePlatform.d.ts +294 -0
  86. package/dist/matterbridgePlatform.d.ts.map +1 -0
  87. package/dist/matterbridgePlatform.js +225 -7
  88. package/dist/matterbridgePlatform.js.map +1 -0
  89. package/dist/matterbridgeTypes.d.ts +188 -0
  90. package/dist/matterbridgeTypes.d.ts.map +1 -0
  91. package/dist/matterbridgeTypes.js +24 -0
  92. package/dist/matterbridgeTypes.js.map +1 -0
  93. package/dist/pluginManager.d.ts +273 -0
  94. package/dist/pluginManager.d.ts.map +1 -0
  95. package/dist/pluginManager.js +264 -3
  96. package/dist/pluginManager.js.map +1 -0
  97. package/dist/roboticVacuumCleaner.d.ts +82 -0
  98. package/dist/roboticVacuumCleaner.d.ts.map +1 -0
  99. package/dist/roboticVacuumCleaner.js +78 -3
  100. package/dist/roboticVacuumCleaner.js.map +1 -0
  101. package/dist/shelly.d.ts +153 -0
  102. package/dist/shelly.d.ts.map +1 -0
  103. package/dist/shelly.js +155 -7
  104. package/dist/shelly.js.map +1 -0
  105. package/dist/storage/export.d.ts +2 -0
  106. package/dist/storage/export.d.ts.map +1 -0
  107. package/dist/storage/export.js +1 -0
  108. package/dist/storage/export.js.map +1 -0
  109. package/dist/update.d.ts +58 -0
  110. package/dist/update.d.ts.map +1 -0
  111. package/dist/update.js +53 -0
  112. package/dist/update.js.map +1 -0
  113. package/dist/utils/colorUtils.d.ts +61 -0
  114. package/dist/utils/colorUtils.d.ts.map +1 -0
  115. package/dist/utils/colorUtils.js +205 -2
  116. package/dist/utils/colorUtils.js.map +1 -0
  117. package/dist/utils/commandLine.d.ts +58 -0
  118. package/dist/utils/commandLine.d.ts.map +1 -0
  119. package/dist/utils/commandLine.js +53 -0
  120. package/dist/utils/commandLine.js.map +1 -0
  121. package/dist/utils/copyDirectory.d.ts +32 -0
  122. package/dist/utils/copyDirectory.d.ts.map +1 -0
  123. package/dist/utils/copyDirectory.js +37 -1
  124. package/dist/utils/copyDirectory.js.map +1 -0
  125. package/dist/utils/createZip.d.ts +38 -0
  126. package/dist/utils/createZip.d.ts.map +1 -0
  127. package/dist/utils/createZip.js +42 -2
  128. package/dist/utils/createZip.js.map +1 -0
  129. package/dist/utils/deepCopy.d.ts +31 -0
  130. package/dist/utils/deepCopy.d.ts.map +1 -0
  131. package/dist/utils/deepCopy.js +38 -0
  132. package/dist/utils/deepCopy.js.map +1 -0
  133. package/dist/utils/deepEqual.d.ts +53 -0
  134. package/dist/utils/deepEqual.d.ts.map +1 -0
  135. package/dist/utils/deepEqual.js +71 -1
  136. package/dist/utils/deepEqual.js.map +1 -0
  137. package/dist/utils/export.d.ts +11 -0
  138. package/dist/utils/export.d.ts.map +1 -0
  139. package/dist/utils/export.js +1 -0
  140. package/dist/utils/export.js.map +1 -0
  141. package/dist/utils/hex.d.ts +48 -0
  142. package/dist/utils/hex.d.ts.map +1 -0
  143. package/dist/utils/hex.js +57 -0
  144. package/dist/utils/hex.js.map +1 -0
  145. package/dist/utils/isvalid.d.ts +102 -0
  146. package/dist/utils/isvalid.d.ts.map +1 -0
  147. package/dist/utils/isvalid.js +100 -0
  148. package/dist/utils/isvalid.js.map +1 -0
  149. package/dist/utils/network.d.ts +69 -0
  150. package/dist/utils/network.d.ts.map +1 -0
  151. package/dist/utils/network.js +76 -5
  152. package/dist/utils/network.js.map +1 -0
  153. package/dist/utils/wait.d.ts +52 -0
  154. package/dist/utils/wait.d.ts.map +1 -0
  155. package/dist/utils/wait.js +58 -9
  156. package/dist/utils/wait.js.map +1 -0
  157. package/dist/waterHeater.d.ts +75 -0
  158. package/dist/waterHeater.d.ts.map +1 -0
  159. package/dist/waterHeater.js +52 -0
  160. package/dist/waterHeater.js.map +1 -0
  161. package/npm-shrinkwrap.json +2 -2
  162. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,28 +1,111 @@
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, Lifecycle } from '@matter/main';
25
+ // Node modules
2
26
  import { createServer } from 'node:http';
3
27
  import https from 'node:https';
4
28
  import os from 'node:os';
5
29
  import path from 'node:path';
6
30
  import { promises as fs } from 'node:fs';
31
+ // Third-party modules
7
32
  import express from 'express';
8
33
  import WebSocket, { WebSocketServer } from 'ws';
9
34
  import multer from 'multer';
35
+ // AnsiLogger module
10
36
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
37
+ // Matterbridge
11
38
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout } from './utils/export.js';
12
39
  import { plg } from './matterbridgeTypes.js';
13
40
  import { hasParameter } from './utils/export.js';
14
41
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
42
+ /**
43
+ * Websocket message ID for logging.
44
+ * @constant {number}
45
+ */
15
46
  export const WS_ID_LOG = 0;
47
+ /**
48
+ * Websocket message ID indicating a refresh is needed.
49
+ * @constant {number}
50
+ */
16
51
  export const WS_ID_REFRESH_NEEDED = 1;
52
+ /**
53
+ * Websocket message ID indicating a restart is needed.
54
+ * @constant {number}
55
+ */
17
56
  export const WS_ID_RESTART_NEEDED = 2;
57
+ /**
58
+ * Websocket message ID indicating a cpu update.
59
+ * @constant {number}
60
+ */
18
61
  export const WS_ID_CPU_UPDATE = 3;
62
+ /**
63
+ * Websocket message ID indicating a memory update.
64
+ * @constant {number}
65
+ */
19
66
  export const WS_ID_MEMORY_UPDATE = 4;
67
+ /**
68
+ * Websocket message ID indicating an uptime update.
69
+ * @constant {number}
70
+ */
20
71
  export const WS_ID_UPTIME_UPDATE = 5;
72
+ /**
73
+ * Websocket message ID indicating a snackbar message.
74
+ * @constant {number}
75
+ */
21
76
  export const WS_ID_SNACKBAR = 6;
77
+ /**
78
+ * Websocket message ID indicating matterbridge has un update available.
79
+ * @constant {number}
80
+ */
22
81
  export const WS_ID_UPDATE_NEEDED = 7;
82
+ /**
83
+ * Websocket message ID indicating a state update.
84
+ * @constant {number}
85
+ */
23
86
  export const WS_ID_STATEUPDATE = 8;
87
+ /**
88
+ * Websocket message ID indicating to close a permanent snackbar message.
89
+ * @constant {number}
90
+ */
24
91
  export const WS_ID_CLOSE_SNACKBAR = 9;
92
+ /**
93
+ * Websocket message ID indicating a shelly system update.
94
+ * check:
95
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
96
+ * perform:
97
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
98
+ * @constant {number}
99
+ */
25
100
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
101
+ /**
102
+ * Websocket message ID indicating a shelly main update.
103
+ * check:
104
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
105
+ * perform:
106
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
107
+ * @constant {number}
108
+ */
26
109
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
27
110
  export class Frontend {
28
111
  matterbridge;
@@ -35,7 +118,7 @@ export class Frontend {
35
118
  webSocketServer;
36
119
  constructor(matterbridge) {
37
120
  this.matterbridge = matterbridge;
38
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
121
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
39
122
  }
40
123
  set logLevel(logLevel) {
41
124
  this.log.logLevel = logLevel;
@@ -43,13 +126,43 @@ export class Frontend {
43
126
  async start(port = 8283) {
44
127
  this.port = port;
45
128
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
129
+ // Initialize multer with the upload directory
46
130
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
47
131
  await fs.mkdir(uploadDir, { recursive: true });
48
132
  const upload = multer({ dest: uploadDir });
133
+ // Create the express app that serves the frontend
49
134
  this.expressApp = express();
135
+ // Inject logging/debug wrapper for route/middleware registration
136
+ /*
137
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
138
+ for (const method of methods) {
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
143
+ try {
144
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
145
+ return original(path, ...rest);
146
+ } catch (err) {
147
+ console.error(`[ERROR] Failed to register route: ${path}`);
148
+ throw err;
149
+ }
150
+ };
151
+ }
152
+ */
153
+ // Log all requests to the server for debugging
154
+ /*
155
+ this.expressApp.use((req, res, next) => {
156
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
157
+ next();
158
+ });
159
+ */
160
+ // Serve static files from '/static' endpoint
50
161
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
51
162
  if (!hasParameter('ssl')) {
163
+ // Create an HTTP server and attach the express app
52
164
  this.httpServer = createServer(this.expressApp);
165
+ // Listen on the specified port
53
166
  if (hasParameter('ingress')) {
54
167
  this.httpServer.listen(this.port, '0.0.0.0', () => {
55
168
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -63,6 +176,7 @@ export class Frontend {
63
176
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
64
177
  });
65
178
  }
179
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
180
  this.httpServer.on('error', (error) => {
67
181
  this.log.error(`Frontend http server error listening on ${this.port}`);
68
182
  switch (error.code) {
@@ -78,6 +192,7 @@ export class Frontend {
78
192
  });
79
193
  }
80
194
  else {
195
+ // Load the SSL certificate, the private key and optionally the CA certificate
81
196
  let cert;
82
197
  try {
83
198
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -105,7 +220,9 @@ export class Frontend {
105
220
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
106
221
  }
107
222
  const serverOptions = { cert, key, ca };
223
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
108
224
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
225
+ // Listen on the specified port
109
226
  if (hasParameter('ingress')) {
110
227
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
111
228
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -119,6 +236,7 @@ export class Frontend {
119
236
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
120
237
  });
121
238
  }
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
240
  this.httpsServer.on('error', (error) => {
123
241
  this.log.error(`Frontend https server error listening on ${this.port}`);
124
242
  switch (error.code) {
@@ -135,16 +253,18 @@ export class Frontend {
135
253
  }
136
254
  if (this.initializeError)
137
255
  return;
256
+ // Create a WebSocket server and attach it to the http or https server
138
257
  const wssPort = this.port;
139
258
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
140
259
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
141
260
  this.webSocketServer.on('connection', (ws, request) => {
142
261
  const clientIp = request.socket.remoteAddress;
143
- let callbackLogLevel = "notice";
144
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
145
- callbackLogLevel = "info";
146
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
147
- callbackLogLevel = "debug";
262
+ // Set the global logger callback for the WebSocketServer
263
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
264
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
265
+ callbackLogLevel = "info" /* LogLevel.INFO */;
266
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
267
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
148
268
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
149
269
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
150
270
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -178,6 +298,7 @@ export class Frontend {
178
298
  this.webSocketServer.on('error', (ws, error) => {
179
299
  this.log.error(`WebSocketServer error: ${error}`);
180
300
  });
301
+ // Subscribe to cli events
181
302
  const { cliEmitter } = await import('./cli.js');
182
303
  cliEmitter.removeAllListeners();
183
304
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -189,6 +310,7 @@ export class Frontend {
189
310
  cliEmitter.on('cpu', (cpuUsage) => {
190
311
  this.wssSendCpuUpdate(cpuUsage);
191
312
  });
313
+ // Endpoint to validate login code
192
314
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
193
315
  const { password } = req.body;
194
316
  this.log.debug('The frontend sent /api/login', password);
@@ -207,23 +329,27 @@ export class Frontend {
207
329
  this.log.warn('/api/login error wrong password');
208
330
  res.json({ valid: false });
209
331
  }
332
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
210
333
  }
211
334
  catch (error) {
212
335
  this.log.error('/api/login error getting password');
213
336
  res.json({ valid: false });
214
337
  }
215
338
  });
339
+ // Endpoint to provide health check for docker
216
340
  this.expressApp.get('/health', (req, res) => {
217
341
  this.log.debug('Express received /health');
218
342
  const healthStatus = {
219
- status: 'ok',
220
- uptime: process.uptime(),
221
- timestamp: new Date().toISOString(),
343
+ status: 'ok', // Indicate service is healthy
344
+ uptime: process.uptime(), // Server uptime in seconds
345
+ timestamp: new Date().toISOString(), // Current timestamp
222
346
  };
223
347
  res.status(200).json(healthStatus);
224
348
  });
349
+ // Endpoint to provide memory usage details
225
350
  this.expressApp.get('/memory', async (req, res) => {
226
351
  this.log.debug('Express received /memory');
352
+ // Memory usage from process
227
353
  const memoryUsageRaw = process.memoryUsage();
228
354
  const memoryUsage = {
229
355
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -232,10 +358,13 @@ export class Frontend {
232
358
  external: this.formatMemoryUsage(memoryUsageRaw.external),
233
359
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
234
360
  };
361
+ // V8 heap statistics
235
362
  const { default: v8 } = await import('node:v8');
236
363
  const heapStatsRaw = v8.getHeapStatistics();
237
364
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
365
+ // Format heapStats
238
366
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
367
+ // Format heapSpaces
239
368
  const heapSpaces = heapSpacesRaw.map((space) => ({
240
369
  ...space,
241
370
  space_size: this.formatMemoryUsage(space.space_size),
@@ -253,19 +382,23 @@ export class Frontend {
253
382
  };
254
383
  res.status(200).json(memoryReport);
255
384
  });
385
+ // Endpoint to provide settings
256
386
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
257
387
  this.log.debug('The frontend sent /api/settings');
258
388
  res.json(await this.getApiSettings());
259
389
  });
390
+ // Endpoint to provide plugins
260
391
  this.expressApp.get('/api/plugins', async (req, res) => {
261
392
  this.log.debug('The frontend sent /api/plugins');
262
393
  res.json(this.getBaseRegisteredPlugins());
263
394
  });
395
+ // Endpoint to provide devices
264
396
  this.expressApp.get('/api/devices', async (req, res) => {
265
397
  this.log.debug('The frontend sent /api/devices');
266
398
  const devices = await this.getDevices();
267
399
  res.json(devices);
268
400
  });
401
+ // Endpoint to view the matterbridge log
269
402
  this.expressApp.get('/api/view-mblog', async (req, res) => {
270
403
  this.log.debug('The frontend sent /api/view-mblog');
271
404
  try {
@@ -278,6 +411,7 @@ export class Frontend {
278
411
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
279
412
  }
280
413
  });
414
+ // Endpoint to view the matter.js log
281
415
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
282
416
  this.log.debug('The frontend sent /api/view-mjlog');
283
417
  try {
@@ -290,6 +424,7 @@ export class Frontend {
290
424
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
291
425
  }
292
426
  });
427
+ // Endpoint to view the shelly log
293
428
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
294
429
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
295
430
  try {
@@ -302,6 +437,7 @@ export class Frontend {
302
437
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
303
438
  }
304
439
  });
440
+ // Endpoint to download the matterbridge log
305
441
  this.expressApp.get('/api/download-mblog', async (req, res) => {
306
442
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
307
443
  try {
@@ -314,6 +450,7 @@ export class Frontend {
314
450
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
315
451
  }
316
452
  res.type('text/plain');
453
+ // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
317
454
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
318
455
  if (error) {
319
456
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -321,6 +458,7 @@ export class Frontend {
321
458
  }
322
459
  });
323
460
  });
461
+ // Endpoint to download the matter log
324
462
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
325
463
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
326
464
  try {
@@ -340,6 +478,7 @@ export class Frontend {
340
478
  }
341
479
  });
342
480
  });
481
+ // Endpoint to download the shelly log
343
482
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
344
483
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
345
484
  try {
@@ -359,6 +498,7 @@ export class Frontend {
359
498
  }
360
499
  });
361
500
  });
501
+ // Endpoint to download the matterbridge storage directory
362
502
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
363
503
  this.log.debug('The frontend sent /api/download-mbstorage');
364
504
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -369,6 +509,7 @@ export class Frontend {
369
509
  }
370
510
  });
371
511
  });
512
+ // Endpoint to download the matter storage file
372
513
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
373
514
  this.log.debug('The frontend sent /api/download-mjstorage');
374
515
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -379,6 +520,7 @@ export class Frontend {
379
520
  }
380
521
  });
381
522
  });
523
+ // Endpoint to download the matterbridge plugin directory
382
524
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
383
525
  this.log.debug('The frontend sent /api/download-pluginstorage');
384
526
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -389,6 +531,7 @@ export class Frontend {
389
531
  }
390
532
  });
391
533
  });
534
+ // Endpoint to download the matterbridge plugin config files
392
535
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
393
536
  this.log.debug('The frontend sent /api/download-pluginconfig');
394
537
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
@@ -399,6 +542,7 @@ export class Frontend {
399
542
  }
400
543
  });
401
544
  });
545
+ // Endpoint to download the matterbridge backup (created with the backup command)
402
546
  this.expressApp.get('/api/download-backup', async (req, res) => {
403
547
  this.log.debug('The frontend sent /api/download-backup');
404
548
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -408,6 +552,7 @@ export class Frontend {
408
552
  }
409
553
  });
410
554
  });
555
+ // Endpoint to upload a package
411
556
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
412
557
  const { filename } = req.body;
413
558
  const file = req.file;
@@ -417,10 +562,13 @@ export class Frontend {
417
562
  return;
418
563
  }
419
564
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
565
+ // Define the path where the plugin file will be saved
420
566
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
421
567
  try {
568
+ // Move the uploaded file to the specified path
422
569
  await fs.rename(file.path, filePath);
423
570
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
571
+ // Install the plugin package
424
572
  if (filename.endsWith('.tgz')) {
425
573
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
426
574
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -439,6 +587,7 @@ export class Frontend {
439
587
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
440
588
  }
441
589
  });
590
+ // Fallback for routing (must be the last route)
442
591
  this.expressApp.use((req, res) => {
443
592
  this.log.debug('The frontend sent:', req.url);
444
593
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -447,12 +596,15 @@ export class Frontend {
447
596
  }
448
597
  async stop() {
449
598
  this.log.debug('Stopping the frontend...');
599
+ // Remove listeners from the express app
450
600
  if (this.expressApp) {
451
601
  this.expressApp.removeAllListeners();
452
602
  this.expressApp = undefined;
453
603
  this.log.debug('Frontend app closed successfully');
454
604
  }
605
+ // Close the WebSocket server
455
606
  if (this.webSocketServer) {
607
+ // Close all active connections
456
608
  this.webSocketServer.clients.forEach((client) => {
457
609
  if (client.readyState === WebSocket.OPEN) {
458
610
  client.close();
@@ -472,6 +624,7 @@ export class Frontend {
472
624
  this.webSocketServer.removeAllListeners();
473
625
  this.webSocketServer = undefined;
474
626
  }
627
+ // Close the http server
475
628
  if (this.httpServer) {
476
629
  await withTimeout(new Promise((resolve) => {
477
630
  this.httpServer?.close((error) => {
@@ -488,6 +641,7 @@ export class Frontend {
488
641
  this.httpServer = undefined;
489
642
  this.log.debug('Frontend http server closed successfully');
490
643
  }
644
+ // Close the https server
491
645
  if (this.httpsServer) {
492
646
  await withTimeout(new Promise((resolve) => {
493
647
  this.httpsServer?.close((error) => {
@@ -506,6 +660,7 @@ export class Frontend {
506
660
  }
507
661
  this.log.debug('Frontend stopped successfully');
508
662
  }
663
+ // Function to format bytes to KB, MB, or GB
509
664
  formatMemoryUsage = (bytes) => {
510
665
  if (bytes >= 1024 ** 3) {
511
666
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -517,6 +672,7 @@ export class Frontend {
517
672
  return `${(bytes / 1024).toFixed(2)} KB`;
518
673
  }
519
674
  };
675
+ // Function to format system uptime with only the most significant unit
520
676
  formatOsUpTime = (seconds) => {
521
677
  if (seconds >= 86400) {
522
678
  const days = Math.floor(seconds / 86400);
@@ -532,8 +688,14 @@ export class Frontend {
532
688
  }
533
689
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
534
690
  };
691
+ /**
692
+ * Retrieves the api settings data.
693
+ *
694
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
695
+ */
535
696
  async getApiSettings() {
536
697
  const { lastCpuUsage } = await import('./cli.js');
698
+ // Update the system information
537
699
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
538
700
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
539
701
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -542,6 +704,7 @@ export class Frontend {
542
704
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
543
705
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
544
706
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
707
+ // Update the matterbridge information
545
708
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
546
709
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
547
710
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -560,6 +723,11 @@ export class Frontend {
560
723
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
561
724
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
562
725
  }
726
+ /**
727
+ * Retrieves the reachable attribute.
728
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
729
+ * @returns {boolean} The reachable attribute.
730
+ */
563
731
  getReachability(device) {
564
732
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
565
733
  return false;
@@ -584,13 +752,20 @@ export class Frontend {
584
752
  }
585
753
  return;
586
754
  };
755
+ // Root endpoint
587
756
  if (device.hasClusterServer(PowerSource.Cluster.id))
588
757
  return powerSource(device);
758
+ // Child endpoints
589
759
  for (const child of device.getChildEndpoints()) {
590
760
  if (child.hasClusterServer(PowerSource.Cluster.id))
591
761
  return powerSource(child);
592
762
  }
593
763
  }
764
+ /**
765
+ * Retrieves the cluster text description from a given device.
766
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
767
+ * @returns {string} The attributes description of the cluster servers in the device.
768
+ */
594
769
  getClusterTextFromDevice(device) {
595
770
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
596
771
  return '';
@@ -632,6 +807,7 @@ export class Frontend {
632
807
  let attributes = '';
633
808
  let supportedModes = [];
634
809
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
810
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
635
811
  if (typeof attributeValue === 'undefined')
636
812
  return;
637
813
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -723,8 +899,13 @@ export class Frontend {
723
899
  if (clusterName === 'userLabel' && attributeName === 'labelList')
724
900
  attributes += `${getUserLabel(device)} `;
725
901
  });
902
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
726
903
  return attributes.trimStart().trimEnd();
727
904
  }
905
+ /**
906
+ * Retrieves the base registered plugins sanitized for res.json().
907
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
908
+ */
728
909
  getBaseRegisteredPlugins() {
729
910
  const baseRegisteredPlugins = [];
730
911
  for (const plugin of this.matterbridge.plugins) {
@@ -763,11 +944,18 @@ export class Frontend {
763
944
  }
764
945
  return baseRegisteredPlugins;
765
946
  }
947
+ /**
948
+ * Retrieves the devices from Matterbridge.
949
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
950
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
951
+ */
766
952
  async getDevices(pluginName) {
767
953
  const devices = [];
768
954
  this.matterbridge.devices.forEach(async (device) => {
955
+ // Filter by pluginName if provided
769
956
  if (pluginName && pluginName !== device.plugin)
770
957
  return;
958
+ // Check if the device has the required properties
771
959
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
772
960
  return;
773
961
  const cluster = this.getClusterTextFromDevice(device);
@@ -787,6 +975,13 @@ export class Frontend {
787
975
  });
788
976
  return devices;
789
977
  }
978
+ /**
979
+ * Handles incoming websocket messages for the Matterbridge frontend.
980
+ *
981
+ * @param {WebSocket} client - The websocket client that sent the message.
982
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
983
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
984
+ */
790
985
  async wsMessageHandler(client, message) {
791
986
  let data;
792
987
  try {
@@ -833,8 +1028,10 @@ export class Frontend {
833
1028
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
834
1029
  const packageName = data.params.packageName.replace(/@.*$/, '');
835
1030
  if (data.params.restart === false && packageName !== 'matterbridge') {
1031
+ // The install comes from InstallPlugins
836
1032
  this.matterbridge.plugins.add(packageName).then((plugin) => {
837
1033
  if (plugin) {
1034
+ // The plugin is not registered
838
1035
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
839
1036
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
840
1037
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
@@ -842,6 +1039,7 @@ export class Frontend {
842
1039
  });
843
1040
  }
844
1041
  else {
1042
+ // The plugin is already registered
845
1043
  this.wssSendSnackbarMessage(`Restart required`, 0);
846
1044
  this.wssSendRefreshRequired('plugins');
847
1045
  this.wssSendRestartRequired();
@@ -849,6 +1047,7 @@ export class Frontend {
849
1047
  });
850
1048
  }
851
1049
  else {
1050
+ // The package is matterbridge
852
1051
  if (this.matterbridge.restartMode !== '') {
853
1052
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
854
1053
  this.matterbridge.shutdownProcess();
@@ -870,6 +1069,7 @@ export class Frontend {
870
1069
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
871
1070
  return;
872
1071
  }
1072
+ // The package is a plugin
873
1073
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
874
1074
  if (plugin) {
875
1075
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -878,6 +1078,7 @@ export class Frontend {
878
1078
  this.wssSendRefreshRequired('plugins');
879
1079
  this.wssSendRefreshRequired('devices');
880
1080
  }
1081
+ // Uninstall the package
881
1082
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
882
1083
  this.matterbridge
883
1084
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1154,6 +1355,7 @@ export class Frontend {
1154
1355
  });
1155
1356
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1156
1357
  deviceTypes = [];
1358
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1157
1359
  const name = childEndpoint.endpoint?.id;
1158
1360
  const clusterServers = childEndpoint.getAllClusterServers();
1159
1361
  clusterServers.forEach((clusterServer) => {
@@ -1267,22 +1469,22 @@ export class Frontend {
1267
1469
  if (isValidString(data.params.value, 4)) {
1268
1470
  this.log.debug('Matterbridge logger level:', data.params.value);
1269
1471
  if (data.params.value === 'Debug') {
1270
- await this.matterbridge.setLogLevel("debug");
1472
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1271
1473
  }
1272
1474
  else if (data.params.value === 'Info') {
1273
- await this.matterbridge.setLogLevel("info");
1475
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1274
1476
  }
1275
1477
  else if (data.params.value === 'Notice') {
1276
- await this.matterbridge.setLogLevel("notice");
1478
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1277
1479
  }
1278
1480
  else if (data.params.value === 'Warn') {
1279
- await this.matterbridge.setLogLevel("warn");
1481
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1280
1482
  }
1281
1483
  else if (data.params.value === 'Error') {
1282
- await this.matterbridge.setLogLevel("error");
1484
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1283
1485
  }
1284
1486
  else if (data.params.value === 'Fatal') {
1285
- await this.matterbridge.setLogLevel("fatal");
1487
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1286
1488
  }
1287
1489
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1288
1490
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1293,6 +1495,7 @@ export class Frontend {
1293
1495
  this.log.debug('Matterbridge file log:', data.params.value);
1294
1496
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1295
1497
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1498
+ // Create the file logger for matterbridge
1296
1499
  if (data.params.value)
1297
1500
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1298
1501
  else
@@ -1457,15 +1660,19 @@ export class Frontend {
1457
1660
  return;
1458
1661
  }
1459
1662
  const config = plugin.configJson;
1663
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1460
1664
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1665
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1461
1666
  if (select === 'serial')
1462
1667
  this.log.info(`Selected device serial ${data.params.serial}`);
1463
1668
  if (select === 'name')
1464
1669
  this.log.info(`Selected device name ${data.params.name}`);
1465
1670
  if (config && select && (select === 'serial' || select === 'name')) {
1671
+ // Remove postfix from the serial if it exists
1466
1672
  if (config.postfix) {
1467
1673
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1468
1674
  }
1675
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1469
1676
  if (isValidArray(config.whiteList, 1)) {
1470
1677
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1471
1678
  config.whiteList.push(data.params.serial);
@@ -1474,6 +1681,7 @@ export class Frontend {
1474
1681
  config.whiteList.push(data.params.name);
1475
1682
  }
1476
1683
  }
1684
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1477
1685
  if (isValidArray(config.blackList, 1)) {
1478
1686
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1479
1687
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1503,7 +1711,9 @@ export class Frontend {
1503
1711
  return;
1504
1712
  }
1505
1713
  const config = plugin.configJson;
1714
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1506
1715
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1716
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1507
1717
  if (select === 'serial')
1508
1718
  this.log.info(`Unselected device serial ${data.params.serial}`);
1509
1719
  if (select === 'name')
@@ -1512,6 +1722,7 @@ export class Frontend {
1512
1722
  if (config.postfix) {
1513
1723
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1514
1724
  }
1725
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1515
1726
  if (isValidArray(config.whiteList, 1)) {
1516
1727
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1517
1728
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1520,6 +1731,7 @@ export class Frontend {
1520
1731
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1521
1732
  }
1522
1733
  }
1734
+ // Add the serial to the blackList
1523
1735
  if (isValidArray(config.blackList)) {
1524
1736
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1525
1737
  config.blackList.push(data.params.serial);
@@ -1552,114 +1764,219 @@ export class Frontend {
1552
1764
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1553
1765
  }
1554
1766
  }
1767
+ /**
1768
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1769
+ *
1770
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1771
+ * @param {string} time - The time string of the message
1772
+ * @param {string} name - The logger name of the message
1773
+ * @param {string} message - The content of the message.
1774
+ *
1775
+ * @remark
1776
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1777
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1778
+ * The function sends the message to all connected clients.
1779
+ */
1555
1780
  wssSendMessage(level, time, name, message) {
1556
1781
  if (!level || !time || !name || !message)
1557
1782
  return;
1783
+ // Remove ANSI escape codes from the message
1784
+ // eslint-disable-next-line no-control-regex
1558
1785
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1786
+ // Remove leading asterisks from the message
1559
1787
  message = message.replace(/^\*+/, '');
1788
+ // Replace all occurrences of \t and \n
1560
1789
  message = message.replace(/[\t\n]/g, '');
1790
+ // Remove non-printable characters
1791
+ // eslint-disable-next-line no-control-regex
1561
1792
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1793
+ // Replace all occurrences of \" with "
1562
1794
  message = message.replace(/\\"/g, '"');
1795
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1563
1796
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1797
+ // Define the maximum allowed length for continuous characters without a space
1564
1798
  const maxContinuousLength = 100;
1565
1799
  const keepStartLength = 20;
1566
1800
  const keepEndLength = 20;
1801
+ // Split the message into words
1567
1802
  message = message
1568
1803
  .split(' ')
1569
1804
  .map((word) => {
1805
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1570
1806
  if (word.length > maxContinuousLength) {
1571
1807
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1572
1808
  }
1573
1809
  return word;
1574
1810
  })
1575
1811
  .join(' ');
1812
+ // Send the message to all connected clients
1576
1813
  this.webSocketServer?.clients.forEach((client) => {
1577
1814
  if (client.readyState === WebSocket.OPEN) {
1578
1815
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1579
1816
  }
1580
1817
  });
1581
1818
  }
1819
+ /**
1820
+ * Sends a need to refresh WebSocket message to all connected clients.
1821
+ *
1822
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1823
+ * possible values:
1824
+ * - 'matterbridgeLatestVersion'
1825
+ * - 'matterbridgeAdvertise'
1826
+ * - 'online'
1827
+ * - 'offline'
1828
+ * - 'reachability'
1829
+ * - 'settings'
1830
+ * - 'plugins'
1831
+ * - 'pluginsRestart'
1832
+ * - 'devices'
1833
+ * - 'fabrics'
1834
+ * - 'sessions'
1835
+ */
1582
1836
  wssSendRefreshRequired(changed = null) {
1583
1837
  this.log.debug('Sending a refresh required message to all connected clients');
1838
+ // Send the message to all connected clients
1584
1839
  this.webSocketServer?.clients.forEach((client) => {
1585
1840
  if (client.readyState === WebSocket.OPEN) {
1586
1841
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1587
1842
  }
1588
1843
  });
1589
1844
  }
1845
+ /**
1846
+ * Sends a need to restart WebSocket message to all connected clients.
1847
+ *
1848
+ */
1590
1849
  wssSendRestartRequired(snackbar = true) {
1591
1850
  this.log.debug('Sending a restart required message to all connected clients');
1592
1851
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1593
1852
  if (snackbar === true)
1594
1853
  this.wssSendSnackbarMessage(`Restart required`, 0);
1854
+ // Send the message to all connected clients
1595
1855
  this.webSocketServer?.clients.forEach((client) => {
1596
1856
  if (client.readyState === WebSocket.OPEN) {
1597
1857
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1598
1858
  }
1599
1859
  });
1600
1860
  }
1861
+ /**
1862
+ * Sends a need to update WebSocket message to all connected clients.
1863
+ *
1864
+ */
1601
1865
  wssSendUpdateRequired() {
1602
1866
  this.log.debug('Sending an update required message to all connected clients');
1603
1867
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1868
+ // Send the message to all connected clients
1604
1869
  this.webSocketServer?.clients.forEach((client) => {
1605
1870
  if (client.readyState === WebSocket.OPEN) {
1606
1871
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1607
1872
  }
1608
1873
  });
1609
1874
  }
1875
+ /**
1876
+ * Sends a cpu update message to all connected clients.
1877
+ *
1878
+ */
1610
1879
  wssSendCpuUpdate(cpuUsage) {
1611
1880
  if (hasParameter('debug'))
1612
1881
  this.log.debug('Sending a cpu update message to all connected clients');
1882
+ // Send the message to all connected clients
1613
1883
  this.webSocketServer?.clients.forEach((client) => {
1614
1884
  if (client.readyState === WebSocket.OPEN) {
1615
1885
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1616
1886
  }
1617
1887
  });
1618
1888
  }
1889
+ /**
1890
+ * Sends a memory update message to all connected clients.
1891
+ *
1892
+ */
1619
1893
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1620
1894
  if (hasParameter('debug'))
1621
1895
  this.log.debug('Sending a memory update message to all connected clients');
1896
+ // Send the message to all connected clients
1622
1897
  this.webSocketServer?.clients.forEach((client) => {
1623
1898
  if (client.readyState === WebSocket.OPEN) {
1624
1899
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1625
1900
  }
1626
1901
  });
1627
1902
  }
1903
+ /**
1904
+ * Sends an uptime update message to all connected clients.
1905
+ *
1906
+ */
1628
1907
  wssSendUptimeUpdate(systemUptime, processUptime) {
1629
1908
  if (hasParameter('debug'))
1630
1909
  this.log.debug('Sending a uptime update message to all connected clients');
1910
+ // Send the message to all connected clients
1631
1911
  this.webSocketServer?.clients.forEach((client) => {
1632
1912
  if (client.readyState === WebSocket.OPEN) {
1633
1913
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1634
1914
  }
1635
1915
  });
1636
1916
  }
1917
+ /**
1918
+ * Sends an open snackbar message to all connected clients.
1919
+ * @param {string} message - The message to send.
1920
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1921
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1922
+ *
1923
+ */
1637
1924
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1638
1925
  this.log.debug('Sending a snackbar message to all connected clients');
1926
+ // Send the message to all connected clients
1639
1927
  this.webSocketServer?.clients.forEach((client) => {
1640
1928
  if (client.readyState === WebSocket.OPEN) {
1641
1929
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1642
1930
  }
1643
1931
  });
1644
1932
  }
1933
+ /**
1934
+ * Sends a close snackbar message to all connected clients.
1935
+ * @param {string} message - The message to send.
1936
+ *
1937
+ */
1645
1938
  wssSendCloseSnackbarMessage(message) {
1646
1939
  this.log.debug('Sending a close snackbar message to all connected clients');
1940
+ // Send the message to all connected clients
1647
1941
  this.webSocketServer?.clients.forEach((client) => {
1648
1942
  if (client.readyState === WebSocket.OPEN) {
1649
1943
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1650
1944
  }
1651
1945
  });
1652
1946
  }
1947
+ /**
1948
+ * Sends an attribute update message to all connected WebSocket clients.
1949
+ *
1950
+ * @param {string | undefined} plugin - The name of the plugin.
1951
+ * @param {string | undefined} serialNumber - The serial number of the device.
1952
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
1953
+ * @param {string} cluster - The cluster name where the attribute belongs.
1954
+ * @param {string} attribute - The name of the attribute that changed.
1955
+ * @param {number | string | boolean} value - The new value of the attribute.
1956
+ *
1957
+ * @remarks
1958
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1959
+ * with the updated attribute information.
1960
+ */
1653
1961
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1654
1962
  this.log.debug('Sending an attribute update message to all connected clients');
1963
+ // Send the message to all connected clients
1655
1964
  this.webSocketServer?.clients.forEach((client) => {
1656
1965
  if (client.readyState === WebSocket.OPEN) {
1657
1966
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1658
1967
  }
1659
1968
  });
1660
1969
  }
1970
+ /**
1971
+ * Sends a message to all connected clients.
1972
+ * @param {number} id - The message id.
1973
+ * @param {string} method - The message method.
1974
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1975
+ *
1976
+ */
1661
1977
  wssBroadcastMessage(id, method, params) {
1662
1978
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1979
+ // Send the message to all connected clients
1663
1980
  this.webSocketServer?.clients.forEach((client) => {
1664
1981
  if (client.readyState === WebSocket.OPEN) {
1665
1982
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1667,3 +1984,4 @@ export class Frontend {
1667
1984
  });
1668
1985
  }
1669
1986
  }
1987
+ //# sourceMappingURL=frontend.js.map