matterbridge 3.0.2-dev-20250515-4122c94 → 3.0.2

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 (158) 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 +334 -15
  20. package/dist/frontend.js.map +1 -0
  21. package/dist/helpers.d.ts +46 -0
  22. package/dist/helpers.d.ts.map +1 -0
  23. package/dist/helpers.js +49 -0
  24. package/dist/helpers.js.map +1 -0
  25. package/dist/index.d.ts +36 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +28 -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 +435 -0
  58. package/dist/matterbridge.d.ts.map +1 -0
  59. package/dist/matterbridge.js +746 -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 +1188 -0
  66. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  67. package/dist/matterbridgeBehaviors.js +53 -4
  68. package/dist/matterbridgeBehaviors.js.map +1 -0
  69. package/dist/matterbridgeDeviceTypes.d.ts +494 -0
  70. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  71. package/dist/matterbridgeDeviceTypes.js +431 -12
  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 +965 -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 +147 -9
  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 +187 -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 +43 -0
  98. package/dist/roboticVacuumCleaner.d.ts.map +1 -0
  99. package/dist/roboticVacuumCleaner.js +39 -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 +51 -0
  154. package/dist/utils/wait.d.ts.map +1 -0
  155. package/dist/utils/wait.js +53 -5
  156. package/dist/utils/wait.js.map +1 -0
  157. package/npm-shrinkwrap.json +2 -2
  158. 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 } 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'));
@@ -446,6 +595,7 @@ export class Frontend {
446
595
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
447
596
  }
448
597
  async stop() {
598
+ // Close the http server
449
599
  if (this.httpServer) {
450
600
  this.httpServer.close((error) => {
451
601
  if (error) {
@@ -459,6 +609,7 @@ export class Frontend {
459
609
  this.httpServer = undefined;
460
610
  this.log.debug('Frontend http server closed successfully');
461
611
  }
612
+ // Close the https server
462
613
  if (this.httpsServer) {
463
614
  this.httpsServer.close((error) => {
464
615
  if (error) {
@@ -472,12 +623,15 @@ export class Frontend {
472
623
  this.httpsServer = undefined;
473
624
  this.log.debug('Frontend https server closed successfully');
474
625
  }
626
+ // Remove listeners from the express app
475
627
  if (this.expressApp) {
476
628
  this.expressApp.removeAllListeners();
477
629
  this.expressApp = undefined;
478
630
  this.log.debug('Frontend app closed successfully');
479
631
  }
632
+ // Close the WebSocket server
480
633
  if (this.webSocketServer) {
634
+ // Close all active connections
481
635
  this.webSocketServer.clients.forEach((client) => {
482
636
  if (client.readyState === WebSocket.OPEN) {
483
637
  client.close();
@@ -491,9 +645,11 @@ export class Frontend {
491
645
  this.log.debug('WebSocket server closed successfully');
492
646
  }
493
647
  });
648
+ // this.webSocketServer.removeAllListeners();
494
649
  this.webSocketServer = undefined;
495
650
  }
496
651
  }
652
+ // Function to format bytes to KB, MB, or GB
497
653
  formatMemoryUsage = (bytes) => {
498
654
  if (bytes >= 1024 ** 3) {
499
655
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -505,6 +661,7 @@ export class Frontend {
505
661
  return `${(bytes / 1024).toFixed(2)} KB`;
506
662
  }
507
663
  };
664
+ // Function to format system uptime with only the most significant unit
508
665
  formatOsUpTime = (seconds) => {
509
666
  if (seconds >= 86400) {
510
667
  const days = Math.floor(seconds / 86400);
@@ -520,8 +677,14 @@ export class Frontend {
520
677
  }
521
678
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
522
679
  };
680
+ /**
681
+ * Retrieves the api settings data.
682
+ *
683
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
684
+ */
523
685
  async getApiSettings() {
524
686
  const { lastCpuUsage } = await import('./cli.js');
687
+ // Update the system information
525
688
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
526
689
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
527
690
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -530,6 +693,7 @@ export class Frontend {
530
693
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
531
694
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
532
695
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
696
+ // Update the matterbridge information
533
697
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
534
698
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
535
699
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -548,6 +712,11 @@ export class Frontend {
548
712
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
549
713
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
550
714
  }
715
+ /**
716
+ * Retrieves the reachable attribute.
717
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
718
+ * @returns {boolean} The reachable attribute.
719
+ */
551
720
  getReachability(device) {
552
721
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
553
722
  return false;
@@ -572,13 +741,20 @@ export class Frontend {
572
741
  }
573
742
  return;
574
743
  };
744
+ // Root endpoint
575
745
  if (device.hasClusterServer(PowerSource.Cluster.id))
576
746
  return powerSource(device);
747
+ // Child endpoints
577
748
  for (const child of device.getChildEndpoints()) {
578
749
  if (child.hasClusterServer(PowerSource.Cluster.id))
579
750
  return powerSource(child);
580
751
  }
581
752
  }
753
+ /**
754
+ * Retrieves the cluster text description from a given device.
755
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
756
+ * @returns {string} The attributes description of the cluster servers in the device.
757
+ */
582
758
  getClusterTextFromDevice(device) {
583
759
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
584
760
  return '';
@@ -620,6 +796,7 @@ export class Frontend {
620
796
  let attributes = '';
621
797
  let supportedModes = [];
622
798
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
799
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
623
800
  if (typeof attributeValue === 'undefined')
624
801
  return;
625
802
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -711,8 +888,13 @@ export class Frontend {
711
888
  if (clusterName === 'userLabel' && attributeName === 'labelList')
712
889
  attributes += `${getUserLabel(device)} `;
713
890
  });
891
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
714
892
  return attributes.trimStart().trimEnd();
715
893
  }
894
+ /**
895
+ * Retrieves the base registered plugins sanitized for res.json().
896
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
897
+ */
716
898
  getBaseRegisteredPlugins() {
717
899
  const baseRegisteredPlugins = [];
718
900
  for (const plugin of this.matterbridge.plugins) {
@@ -751,11 +933,18 @@ export class Frontend {
751
933
  }
752
934
  return baseRegisteredPlugins;
753
935
  }
936
+ /**
937
+ * Retrieves the devices from Matterbridge.
938
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
939
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
940
+ */
754
941
  async getDevices(pluginName) {
755
942
  const devices = [];
756
943
  this.matterbridge.devices.forEach(async (device) => {
944
+ // Filter by pluginName if provided
757
945
  if (pluginName && pluginName !== device.plugin)
758
946
  return;
947
+ // Check if the device has the required properties
759
948
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
760
949
  return;
761
950
  const cluster = this.getClusterTextFromDevice(device);
@@ -775,6 +964,13 @@ export class Frontend {
775
964
  });
776
965
  return devices;
777
966
  }
967
+ /**
968
+ * Handles incoming websocket messages for the Matterbridge frontend.
969
+ *
970
+ * @param {WebSocket} client - The websocket client that sent the message.
971
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
972
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
973
+ */
778
974
  async wsMessageHandler(client, message) {
779
975
  let data;
780
976
  try {
@@ -821,8 +1017,10 @@ export class Frontend {
821
1017
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
822
1018
  const packageName = data.params.packageName.replace(/@.*$/, '');
823
1019
  if (data.params.restart === false && packageName !== 'matterbridge') {
1020
+ // The install comes from InstallPlugins
824
1021
  this.matterbridge.plugins.add(packageName).then((plugin) => {
825
1022
  if (plugin) {
1023
+ // The plugin is not registered
826
1024
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
827
1025
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
828
1026
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
@@ -830,6 +1028,7 @@ export class Frontend {
830
1028
  });
831
1029
  }
832
1030
  else {
1031
+ // The plugin is already registered
833
1032
  this.wssSendSnackbarMessage(`Restart required`, 0);
834
1033
  this.wssSendRefreshRequired('plugins');
835
1034
  this.wssSendRestartRequired();
@@ -837,6 +1036,7 @@ export class Frontend {
837
1036
  });
838
1037
  }
839
1038
  else {
1039
+ // The package is matterbridge
840
1040
  if (this.matterbridge.restartMode !== '') {
841
1041
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
842
1042
  this.matterbridge.shutdownProcess();
@@ -858,6 +1058,7 @@ export class Frontend {
858
1058
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
859
1059
  return;
860
1060
  }
1061
+ // The package is a plugin
861
1062
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
862
1063
  if (plugin) {
863
1064
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -866,6 +1067,7 @@ export class Frontend {
866
1067
  this.wssSendRefreshRequired('plugins');
867
1068
  this.wssSendRefreshRequired('devices');
868
1069
  }
1070
+ // Uninstall the package
869
1071
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
870
1072
  this.matterbridge
871
1073
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1142,6 +1344,7 @@ export class Frontend {
1142
1344
  });
1143
1345
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1144
1346
  deviceTypes = [];
1347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1145
1348
  const name = childEndpoint.endpoint?.id;
1146
1349
  const clusterServers = childEndpoint.getAllClusterServers();
1147
1350
  clusterServers.forEach((clusterServer) => {
@@ -1255,22 +1458,22 @@ export class Frontend {
1255
1458
  if (isValidString(data.params.value, 4)) {
1256
1459
  this.log.debug('Matterbridge logger level:', data.params.value);
1257
1460
  if (data.params.value === 'Debug') {
1258
- await this.matterbridge.setLogLevel("debug");
1461
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1259
1462
  }
1260
1463
  else if (data.params.value === 'Info') {
1261
- await this.matterbridge.setLogLevel("info");
1464
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1262
1465
  }
1263
1466
  else if (data.params.value === 'Notice') {
1264
- await this.matterbridge.setLogLevel("notice");
1467
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1265
1468
  }
1266
1469
  else if (data.params.value === 'Warn') {
1267
- await this.matterbridge.setLogLevel("warn");
1470
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1268
1471
  }
1269
1472
  else if (data.params.value === 'Error') {
1270
- await this.matterbridge.setLogLevel("error");
1473
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1271
1474
  }
1272
1475
  else if (data.params.value === 'Fatal') {
1273
- await this.matterbridge.setLogLevel("fatal");
1476
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1274
1477
  }
1275
1478
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1276
1479
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1281,6 +1484,7 @@ export class Frontend {
1281
1484
  this.log.debug('Matterbridge file log:', data.params.value);
1282
1485
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1283
1486
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1487
+ // Create the file logger for matterbridge
1284
1488
  if (data.params.value)
1285
1489
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1286
1490
  else
@@ -1436,15 +1640,19 @@ export class Frontend {
1436
1640
  return;
1437
1641
  }
1438
1642
  const config = plugin.configJson;
1643
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1439
1644
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1645
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1440
1646
  if (select === 'serial')
1441
1647
  this.log.info(`Selected device serial ${data.params.serial}`);
1442
1648
  if (select === 'name')
1443
1649
  this.log.info(`Selected device name ${data.params.name}`);
1444
1650
  if (config && select && (select === 'serial' || select === 'name')) {
1651
+ // Remove postfix from the serial if it exists
1445
1652
  if (config.postfix) {
1446
1653
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1447
1654
  }
1655
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1448
1656
  if (isValidArray(config.whiteList, 1)) {
1449
1657
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1450
1658
  config.whiteList.push(data.params.serial);
@@ -1453,6 +1661,7 @@ export class Frontend {
1453
1661
  config.whiteList.push(data.params.name);
1454
1662
  }
1455
1663
  }
1664
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1456
1665
  if (isValidArray(config.blackList, 1)) {
1457
1666
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1458
1667
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1482,7 +1691,9 @@ export class Frontend {
1482
1691
  return;
1483
1692
  }
1484
1693
  const config = plugin.configJson;
1694
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1485
1695
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1696
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1486
1697
  if (select === 'serial')
1487
1698
  this.log.info(`Unselected device serial ${data.params.serial}`);
1488
1699
  if (select === 'name')
@@ -1491,6 +1702,7 @@ export class Frontend {
1491
1702
  if (config.postfix) {
1492
1703
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1493
1704
  }
1705
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1494
1706
  if (isValidArray(config.whiteList, 1)) {
1495
1707
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1496
1708
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1499,6 +1711,7 @@ export class Frontend {
1499
1711
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1500
1712
  }
1501
1713
  }
1714
+ // Add the serial to the blackList
1502
1715
  if (isValidArray(config.blackList)) {
1503
1716
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1504
1717
  config.blackList.push(data.params.serial);
@@ -1531,114 +1744,219 @@ export class Frontend {
1531
1744
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1532
1745
  }
1533
1746
  }
1747
+ /**
1748
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1749
+ *
1750
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1751
+ * @param {string} time - The time string of the message
1752
+ * @param {string} name - The logger name of the message
1753
+ * @param {string} message - The content of the message.
1754
+ *
1755
+ * @remark
1756
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1757
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1758
+ * The function sends the message to all connected clients.
1759
+ */
1534
1760
  wssSendMessage(level, time, name, message) {
1535
1761
  if (!level || !time || !name || !message)
1536
1762
  return;
1763
+ // Remove ANSI escape codes from the message
1764
+ // eslint-disable-next-line no-control-regex
1537
1765
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1766
+ // Remove leading asterisks from the message
1538
1767
  message = message.replace(/^\*+/, '');
1768
+ // Replace all occurrences of \t and \n
1539
1769
  message = message.replace(/[\t\n]/g, '');
1770
+ // Remove non-printable characters
1771
+ // eslint-disable-next-line no-control-regex
1540
1772
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1773
+ // Replace all occurrences of \" with "
1541
1774
  message = message.replace(/\\"/g, '"');
1775
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1542
1776
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1777
+ // Define the maximum allowed length for continuous characters without a space
1543
1778
  const maxContinuousLength = 100;
1544
1779
  const keepStartLength = 20;
1545
1780
  const keepEndLength = 20;
1781
+ // Split the message into words
1546
1782
  message = message
1547
1783
  .split(' ')
1548
1784
  .map((word) => {
1785
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1549
1786
  if (word.length > maxContinuousLength) {
1550
1787
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1551
1788
  }
1552
1789
  return word;
1553
1790
  })
1554
1791
  .join(' ');
1792
+ // Send the message to all connected clients
1555
1793
  this.webSocketServer?.clients.forEach((client) => {
1556
1794
  if (client.readyState === WebSocket.OPEN) {
1557
1795
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1558
1796
  }
1559
1797
  });
1560
1798
  }
1799
+ /**
1800
+ * Sends a need to refresh WebSocket message to all connected clients.
1801
+ *
1802
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1803
+ * possible values:
1804
+ * - 'matterbridgeLatestVersion'
1805
+ * - 'matterbridgeAdvertise'
1806
+ * - 'online'
1807
+ * - 'offline'
1808
+ * - 'reachability'
1809
+ * - 'settings'
1810
+ * - 'plugins'
1811
+ * - 'pluginsRestart'
1812
+ * - 'devices'
1813
+ * - 'fabrics'
1814
+ * - 'sessions'
1815
+ */
1561
1816
  wssSendRefreshRequired(changed = null) {
1562
1817
  this.log.debug('Sending a refresh required message to all connected clients');
1818
+ // Send the message to all connected clients
1563
1819
  this.webSocketServer?.clients.forEach((client) => {
1564
1820
  if (client.readyState === WebSocket.OPEN) {
1565
1821
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1566
1822
  }
1567
1823
  });
1568
1824
  }
1825
+ /**
1826
+ * Sends a need to restart WebSocket message to all connected clients.
1827
+ *
1828
+ */
1569
1829
  wssSendRestartRequired(snackbar = true) {
1570
1830
  this.log.debug('Sending a restart required message to all connected clients');
1571
1831
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1572
1832
  if (snackbar === true)
1573
1833
  this.wssSendSnackbarMessage(`Restart required`, 0);
1834
+ // Send the message to all connected clients
1574
1835
  this.webSocketServer?.clients.forEach((client) => {
1575
1836
  if (client.readyState === WebSocket.OPEN) {
1576
1837
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1577
1838
  }
1578
1839
  });
1579
1840
  }
1841
+ /**
1842
+ * Sends a need to update WebSocket message to all connected clients.
1843
+ *
1844
+ */
1580
1845
  wssSendUpdateRequired() {
1581
1846
  this.log.debug('Sending an update required message to all connected clients');
1582
1847
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1848
+ // Send the message to all connected clients
1583
1849
  this.webSocketServer?.clients.forEach((client) => {
1584
1850
  if (client.readyState === WebSocket.OPEN) {
1585
1851
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1586
1852
  }
1587
1853
  });
1588
1854
  }
1855
+ /**
1856
+ * Sends a cpu update message to all connected clients.
1857
+ *
1858
+ */
1589
1859
  wssSendCpuUpdate(cpuUsage) {
1590
1860
  if (hasParameter('debug'))
1591
1861
  this.log.debug('Sending a cpu update message to all connected clients');
1862
+ // Send the message to all connected clients
1592
1863
  this.webSocketServer?.clients.forEach((client) => {
1593
1864
  if (client.readyState === WebSocket.OPEN) {
1594
1865
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1595
1866
  }
1596
1867
  });
1597
1868
  }
1869
+ /**
1870
+ * Sends a memory update message to all connected clients.
1871
+ *
1872
+ */
1598
1873
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1599
1874
  if (hasParameter('debug'))
1600
1875
  this.log.debug('Sending a memory update message to all connected clients');
1876
+ // Send the message to all connected clients
1601
1877
  this.webSocketServer?.clients.forEach((client) => {
1602
1878
  if (client.readyState === WebSocket.OPEN) {
1603
1879
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1604
1880
  }
1605
1881
  });
1606
1882
  }
1883
+ /**
1884
+ * Sends an uptime update message to all connected clients.
1885
+ *
1886
+ */
1607
1887
  wssSendUptimeUpdate(systemUptime, processUptime) {
1608
1888
  if (hasParameter('debug'))
1609
1889
  this.log.debug('Sending a uptime update message to all connected clients');
1890
+ // Send the message to all connected clients
1610
1891
  this.webSocketServer?.clients.forEach((client) => {
1611
1892
  if (client.readyState === WebSocket.OPEN) {
1612
1893
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1613
1894
  }
1614
1895
  });
1615
1896
  }
1897
+ /**
1898
+ * Sends an open snackbar message to all connected clients.
1899
+ * @param {string} message - The message to send.
1900
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1901
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1902
+ *
1903
+ */
1616
1904
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1617
1905
  this.log.debug('Sending a snackbar message to all connected clients');
1906
+ // Send the message to all connected clients
1618
1907
  this.webSocketServer?.clients.forEach((client) => {
1619
1908
  if (client.readyState === WebSocket.OPEN) {
1620
1909
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1621
1910
  }
1622
1911
  });
1623
1912
  }
1913
+ /**
1914
+ * Sends a close snackbar message to all connected clients.
1915
+ * @param {string} message - The message to send.
1916
+ *
1917
+ */
1624
1918
  wssSendCloseSnackbarMessage(message) {
1625
1919
  this.log.debug('Sending a close snackbar message to all connected clients');
1920
+ // Send the message to all connected clients
1626
1921
  this.webSocketServer?.clients.forEach((client) => {
1627
1922
  if (client.readyState === WebSocket.OPEN) {
1628
1923
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1629
1924
  }
1630
1925
  });
1631
1926
  }
1927
+ /**
1928
+ * Sends an attribute update message to all connected WebSocket clients.
1929
+ *
1930
+ * @param {string | undefined} plugin - The name of the plugin.
1931
+ * @param {string | undefined} serialNumber - The serial number of the device.
1932
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
1933
+ * @param {string} cluster - The cluster name where the attribute belongs.
1934
+ * @param {string} attribute - The name of the attribute that changed.
1935
+ * @param {number | string | boolean} value - The new value of the attribute.
1936
+ *
1937
+ * @remarks
1938
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1939
+ * with the updated attribute information.
1940
+ */
1632
1941
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1633
1942
  this.log.debug('Sending an attribute update message to all connected clients');
1943
+ // Send the message to all connected clients
1634
1944
  this.webSocketServer?.clients.forEach((client) => {
1635
1945
  if (client.readyState === WebSocket.OPEN) {
1636
1946
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1637
1947
  }
1638
1948
  });
1639
1949
  }
1950
+ /**
1951
+ * Sends a message to all connected clients.
1952
+ * @param {number} id - The message id.
1953
+ * @param {string} method - The message method.
1954
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1955
+ *
1956
+ */
1640
1957
  wssBroadcastMessage(id, method, params) {
1641
1958
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1959
+ // Send the message to all connected clients
1642
1960
  this.webSocketServer?.clients.forEach((client) => {
1643
1961
  if (client.readyState === WebSocket.OPEN) {
1644
1962
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1646,3 +1964,4 @@ export class Frontend {
1646
1964
  });
1647
1965
  }
1648
1966
  }
1967
+ //# sourceMappingURL=frontend.js.map