matterbridge 3.1.1-dev-20250704-aff5fcb → 3.1.1

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 (208) hide show
  1. package/CHANGELOG.md +4 -3
  2. package/README.md +5 -25
  3. package/dist/cli.d.ts +26 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +93 -6
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cliEmitter.d.ts +34 -0
  8. package/dist/cliEmitter.d.ts.map +1 -0
  9. package/dist/cliEmitter.js +36 -0
  10. package/dist/cliEmitter.js.map +1 -0
  11. package/dist/clusters/export.d.ts +2 -0
  12. package/dist/clusters/export.d.ts.map +1 -0
  13. package/dist/clusters/export.js +2 -0
  14. package/dist/clusters/export.js.map +1 -0
  15. package/dist/defaultConfigSchema.d.ts +28 -0
  16. package/dist/defaultConfigSchema.d.ts.map +1 -0
  17. package/dist/defaultConfigSchema.js +24 -0
  18. package/dist/defaultConfigSchema.js.map +1 -0
  19. package/dist/deviceManager.d.ts +112 -0
  20. package/dist/deviceManager.d.ts.map +1 -0
  21. package/dist/deviceManager.js +94 -1
  22. package/dist/deviceManager.js.map +1 -0
  23. package/dist/devices/batteryStorage.d.ts +48 -0
  24. package/dist/devices/batteryStorage.d.ts.map +1 -0
  25. package/dist/devices/batteryStorage.js +48 -1
  26. package/dist/devices/batteryStorage.js.map +1 -0
  27. package/dist/devices/evse.d.ts +75 -0
  28. package/dist/devices/evse.d.ts.map +1 -0
  29. package/dist/devices/evse.js +74 -10
  30. package/dist/devices/evse.js.map +1 -0
  31. package/dist/devices/export.d.ts +9 -0
  32. package/dist/devices/export.d.ts.map +1 -0
  33. package/dist/devices/export.js +2 -0
  34. package/dist/devices/export.js.map +1 -0
  35. package/dist/devices/heatPump.d.ts +47 -0
  36. package/dist/devices/heatPump.d.ts.map +1 -0
  37. package/dist/devices/heatPump.js +50 -2
  38. package/dist/devices/heatPump.js.map +1 -0
  39. package/dist/devices/laundryDryer.d.ts +87 -0
  40. package/dist/devices/laundryDryer.d.ts.map +1 -0
  41. package/dist/devices/laundryDryer.js +83 -6
  42. package/dist/devices/laundryDryer.js.map +1 -0
  43. package/dist/devices/laundryWasher.d.ts +242 -0
  44. package/dist/devices/laundryWasher.d.ts.map +1 -0
  45. package/dist/devices/laundryWasher.js +91 -7
  46. package/dist/devices/laundryWasher.js.map +1 -0
  47. package/dist/devices/roboticVacuumCleaner.d.ts +103 -0
  48. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  49. package/dist/devices/roboticVacuumCleaner.js +82 -6
  50. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  51. package/dist/devices/solarPower.d.ts +40 -0
  52. package/dist/devices/solarPower.d.ts.map +1 -0
  53. package/dist/devices/solarPower.js +38 -0
  54. package/dist/devices/solarPower.js.map +1 -0
  55. package/dist/devices/waterHeater.d.ts +111 -0
  56. package/dist/devices/waterHeater.d.ts.map +1 -0
  57. package/dist/devices/waterHeater.js +82 -2
  58. package/dist/devices/waterHeater.js.map +1 -0
  59. package/dist/frontend.d.ts +302 -0
  60. package/dist/frontend.d.ts.map +1 -0
  61. package/dist/frontend.js +439 -20
  62. package/dist/frontend.js.map +1 -0
  63. package/dist/globalMatterbridge.d.ts +59 -0
  64. package/dist/globalMatterbridge.d.ts.map +1 -0
  65. package/dist/globalMatterbridge.js +47 -0
  66. package/dist/globalMatterbridge.js.map +1 -0
  67. package/dist/helpers.d.ts +48 -0
  68. package/dist/helpers.d.ts.map +1 -0
  69. package/dist/helpers.js +53 -0
  70. package/dist/helpers.js.map +1 -0
  71. package/dist/index.d.ts +41 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +31 -1
  74. package/dist/index.js.map +1 -0
  75. package/dist/logger/export.d.ts +2 -0
  76. package/dist/logger/export.d.ts.map +1 -0
  77. package/dist/logger/export.js +1 -0
  78. package/dist/logger/export.js.map +1 -0
  79. package/dist/matter/behaviors.d.ts +2 -0
  80. package/dist/matter/behaviors.d.ts.map +1 -0
  81. package/dist/matter/behaviors.js +2 -0
  82. package/dist/matter/behaviors.js.map +1 -0
  83. package/dist/matter/clusters.d.ts +2 -0
  84. package/dist/matter/clusters.d.ts.map +1 -0
  85. package/dist/matter/clusters.js +2 -0
  86. package/dist/matter/clusters.js.map +1 -0
  87. package/dist/matter/devices.d.ts +2 -0
  88. package/dist/matter/devices.d.ts.map +1 -0
  89. package/dist/matter/devices.js +2 -0
  90. package/dist/matter/devices.js.map +1 -0
  91. package/dist/matter/endpoints.d.ts +2 -0
  92. package/dist/matter/endpoints.d.ts.map +1 -0
  93. package/dist/matter/endpoints.js +2 -0
  94. package/dist/matter/endpoints.js.map +1 -0
  95. package/dist/matter/export.d.ts +5 -0
  96. package/dist/matter/export.d.ts.map +1 -0
  97. package/dist/matter/export.js +3 -0
  98. package/dist/matter/export.js.map +1 -0
  99. package/dist/matter/types.d.ts +3 -0
  100. package/dist/matter/types.d.ts.map +1 -0
  101. package/dist/matter/types.js +3 -0
  102. package/dist/matter/types.js.map +1 -0
  103. package/dist/matterbridge.d.ts +450 -0
  104. package/dist/matterbridge.d.ts.map +1 -0
  105. package/dist/matterbridge.js +802 -50
  106. package/dist/matterbridge.js.map +1 -0
  107. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  108. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  109. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  110. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  111. package/dist/matterbridgeBehaviors.d.ts +1340 -0
  112. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  113. package/dist/matterbridgeBehaviors.js +61 -1
  114. package/dist/matterbridgeBehaviors.js.map +1 -0
  115. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  116. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  117. package/dist/matterbridgeDeviceTypes.js +579 -15
  118. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  119. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  120. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  121. package/dist/matterbridgeDynamicPlatform.js +36 -0
  122. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  123. package/dist/matterbridgeEndpoint.d.ts +1179 -0
  124. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  125. package/dist/matterbridgeEndpoint.js +1027 -42
  126. package/dist/matterbridgeEndpoint.js.map +1 -0
  127. package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
  128. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  129. package/dist/matterbridgeEndpointHelpers.js +322 -12
  130. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  131. package/dist/matterbridgePlatform.d.ts +310 -0
  132. package/dist/matterbridgePlatform.d.ts.map +1 -0
  133. package/dist/matterbridgePlatform.js +233 -0
  134. package/dist/matterbridgePlatform.js.map +1 -0
  135. package/dist/matterbridgeTypes.d.ts +192 -0
  136. package/dist/matterbridgeTypes.d.ts.map +1 -0
  137. package/dist/matterbridgeTypes.js +25 -0
  138. package/dist/matterbridgeTypes.js.map +1 -0
  139. package/dist/pluginManager.d.ts +291 -0
  140. package/dist/pluginManager.d.ts.map +1 -0
  141. package/dist/pluginManager.js +269 -5
  142. package/dist/pluginManager.js.map +1 -0
  143. package/dist/shelly.d.ts +174 -0
  144. package/dist/shelly.d.ts.map +1 -0
  145. package/dist/shelly.js +168 -7
  146. package/dist/shelly.js.map +1 -0
  147. package/dist/storage/export.d.ts +2 -0
  148. package/dist/storage/export.d.ts.map +1 -0
  149. package/dist/storage/export.js +1 -0
  150. package/dist/storage/export.js.map +1 -0
  151. package/dist/update.d.ts +59 -0
  152. package/dist/update.d.ts.map +1 -0
  153. package/dist/update.js +54 -0
  154. package/dist/update.js.map +1 -0
  155. package/dist/utils/colorUtils.d.ts +117 -0
  156. package/dist/utils/colorUtils.d.ts.map +1 -0
  157. package/dist/utils/colorUtils.js +263 -2
  158. package/dist/utils/colorUtils.js.map +1 -0
  159. package/dist/utils/commandLine.d.ts +59 -0
  160. package/dist/utils/commandLine.d.ts.map +1 -0
  161. package/dist/utils/commandLine.js +54 -0
  162. package/dist/utils/commandLine.js.map +1 -0
  163. package/dist/utils/copyDirectory.d.ts +33 -0
  164. package/dist/utils/copyDirectory.d.ts.map +1 -0
  165. package/dist/utils/copyDirectory.js +38 -1
  166. package/dist/utils/copyDirectory.js.map +1 -0
  167. package/dist/utils/createDirectory.d.ts +34 -0
  168. package/dist/utils/createDirectory.d.ts.map +1 -0
  169. package/dist/utils/createDirectory.js +33 -0
  170. package/dist/utils/createDirectory.js.map +1 -0
  171. package/dist/utils/createZip.d.ts +39 -0
  172. package/dist/utils/createZip.d.ts.map +1 -0
  173. package/dist/utils/createZip.js +47 -2
  174. package/dist/utils/createZip.js.map +1 -0
  175. package/dist/utils/deepCopy.d.ts +32 -0
  176. package/dist/utils/deepCopy.d.ts.map +1 -0
  177. package/dist/utils/deepCopy.js +39 -0
  178. package/dist/utils/deepCopy.js.map +1 -0
  179. package/dist/utils/deepEqual.d.ts +54 -0
  180. package/dist/utils/deepEqual.d.ts.map +1 -0
  181. package/dist/utils/deepEqual.js +72 -1
  182. package/dist/utils/deepEqual.js.map +1 -0
  183. package/dist/utils/export.d.ts +12 -0
  184. package/dist/utils/export.d.ts.map +1 -0
  185. package/dist/utils/export.js +1 -0
  186. package/dist/utils/export.js.map +1 -0
  187. package/dist/utils/hex.d.ts +49 -0
  188. package/dist/utils/hex.d.ts.map +1 -0
  189. package/dist/utils/hex.js +58 -0
  190. package/dist/utils/hex.js.map +1 -0
  191. package/dist/utils/isvalid.d.ts +103 -0
  192. package/dist/utils/isvalid.d.ts.map +1 -0
  193. package/dist/utils/isvalid.js +101 -0
  194. package/dist/utils/isvalid.js.map +1 -0
  195. package/dist/utils/network.d.ts +76 -0
  196. package/dist/utils/network.d.ts.map +1 -0
  197. package/dist/utils/network.js +83 -5
  198. package/dist/utils/network.js.map +1 -0
  199. package/dist/utils/spawn.d.ts +11 -0
  200. package/dist/utils/spawn.d.ts.map +1 -0
  201. package/dist/utils/spawn.js +18 -0
  202. package/dist/utils/spawn.js.map +1 -0
  203. package/dist/utils/wait.d.ts +56 -0
  204. package/dist/utils/wait.d.ts.map +1 -0
  205. package/dist/utils/wait.js +62 -9
  206. package/dist/utils/wait.js.map +1 -0
  207. package/npm-shrinkwrap.json +2 -2
  208. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,29 +1,126 @@
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @created 2025-01-13
7
+ * @version 1.1.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2025, 2026, 2027 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // Node modules
1
25
  import { createServer } from 'node:http';
2
26
  import https from 'node:https';
3
27
  import os from 'node:os';
4
28
  import path from 'node:path';
5
29
  import { promises as fs } from 'node:fs';
6
30
  import EventEmitter from 'node:events';
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 'node-ansi-logger';
37
+ // @matter
11
38
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
12
39
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
40
+ // Matterbridge
13
41
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
14
42
  import { plg } from './matterbridgeTypes.js';
15
43
  import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
44
+ import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
45
+ /**
46
+ * Websocket message ID for logging.
47
+ *
48
+ * @constant {number}
49
+ */
16
50
  export const WS_ID_LOG = 0;
51
+ /**
52
+ * Websocket message ID indicating a refresh is needed.
53
+ *
54
+ * @constant {number}
55
+ */
17
56
  export const WS_ID_REFRESH_NEEDED = 1;
57
+ /**
58
+ * Websocket message ID indicating a restart is needed.
59
+ *
60
+ * @constant {number}
61
+ */
18
62
  export const WS_ID_RESTART_NEEDED = 2;
63
+ /**
64
+ * Websocket message ID indicating a cpu update.
65
+ *
66
+ * @constant {number}
67
+ */
19
68
  export const WS_ID_CPU_UPDATE = 3;
69
+ /**
70
+ * Websocket message ID indicating a memory update.
71
+ *
72
+ * @constant {number}
73
+ */
20
74
  export const WS_ID_MEMORY_UPDATE = 4;
75
+ /**
76
+ * Websocket message ID indicating an uptime update.
77
+ *
78
+ * @constant {number}
79
+ */
21
80
  export const WS_ID_UPTIME_UPDATE = 5;
81
+ /**
82
+ * Websocket message ID indicating a snackbar message.
83
+ *
84
+ * @constant {number}
85
+ */
22
86
  export const WS_ID_SNACKBAR = 6;
87
+ /**
88
+ * Websocket message ID indicating matterbridge has un update available.
89
+ *
90
+ * @constant {number}
91
+ */
23
92
  export const WS_ID_UPDATE_NEEDED = 7;
93
+ /**
94
+ * Websocket message ID indicating a state update.
95
+ *
96
+ * @constant {number}
97
+ */
24
98
  export const WS_ID_STATEUPDATE = 8;
99
+ /**
100
+ * Websocket message ID indicating to close a permanent snackbar message.
101
+ *
102
+ * @constant {number}
103
+ */
25
104
  export const WS_ID_CLOSE_SNACKBAR = 9;
105
+ /**
106
+ * Websocket message ID indicating a shelly system update.
107
+ * check:
108
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
109
+ * perform:
110
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
111
+ *
112
+ * @constant {number}
113
+ */
26
114
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
115
+ /**
116
+ * Websocket message ID indicating a shelly main update.
117
+ * check:
118
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
119
+ * perform:
120
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
121
+ *
122
+ * @constant {number}
123
+ */
27
124
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
28
125
  export class Frontend extends EventEmitter {
29
126
  matterbridge;
@@ -37,7 +134,7 @@ export class Frontend extends EventEmitter {
37
134
  constructor(matterbridge) {
38
135
  super();
39
136
  this.matterbridge = matterbridge;
40
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
137
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
41
138
  }
42
139
  set logLevel(logLevel) {
43
140
  this.log.logLevel = logLevel;
@@ -45,13 +142,50 @@ export class Frontend extends EventEmitter {
45
142
  async start(port = 8283) {
46
143
  this.port = port;
47
144
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
145
+ // Initialize multer with the upload directory
48
146
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
49
147
  await fs.mkdir(uploadDir, { recursive: true });
50
148
  const upload = multer({ dest: uploadDir });
149
+ // Create the express app that serves the frontend
51
150
  this.expressApp = express();
151
+ // Inject logging/debug wrapper for route/middleware registration
152
+ /*
153
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
154
+ for (const method of methods) {
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
159
+ try {
160
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
161
+ return original(path, ...rest);
162
+ } catch (err) {
163
+ console.error(`[ERROR] Failed to register route: ${path}`);
164
+ throw err;
165
+ }
166
+ };
167
+ }
168
+ */
169
+ // Log all requests to the server for debugging
170
+ /*
171
+ this.expressApp.use((req, res, next) => {
172
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
173
+ next();
174
+ });
175
+ */
176
+ // Serve static files from '/static' endpoint
52
177
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
53
178
  if (!hasParameter('ssl')) {
54
- this.httpServer = createServer(this.expressApp);
179
+ // Create an HTTP server and attach the express app
180
+ try {
181
+ this.httpServer = createServer(this.expressApp);
182
+ }
183
+ catch (error) {
184
+ this.log.error(`Failed to create HTTP server: ${error}`);
185
+ this.emit('server_error', error);
186
+ return;
187
+ }
188
+ // Listen on the specified port
55
189
  if (hasParameter('ingress')) {
56
190
  this.httpServer.listen(this.port, '0.0.0.0', () => {
57
191
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -83,6 +217,7 @@ export class Frontend extends EventEmitter {
83
217
  });
84
218
  }
85
219
  else {
220
+ // Load the SSL certificate, the private key and optionally the CA certificate
86
221
  let cert;
87
222
  try {
88
223
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -90,6 +225,7 @@ export class Frontend extends EventEmitter {
90
225
  }
91
226
  catch (error) {
92
227
  this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
228
+ this.emit('server_error', error);
93
229
  return;
94
230
  }
95
231
  let key;
@@ -99,6 +235,7 @@ export class Frontend extends EventEmitter {
99
235
  }
100
236
  catch (error) {
101
237
  this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
238
+ this.emit('server_error', error);
102
239
  return;
103
240
  }
104
241
  let ca;
@@ -110,7 +247,16 @@ export class Frontend extends EventEmitter {
110
247
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
111
248
  }
112
249
  const serverOptions = { cert, key, ca };
113
- this.httpsServer = https.createServer(serverOptions, this.expressApp);
250
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
251
+ try {
252
+ this.httpsServer = https.createServer(serverOptions, this.expressApp);
253
+ }
254
+ catch (error) {
255
+ this.log.error(`Failed to create HTTPS server: ${error}`);
256
+ this.emit('server_error', error);
257
+ return;
258
+ }
259
+ // Listen on the specified port
114
260
  if (hasParameter('ingress')) {
115
261
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
116
262
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -143,16 +289,18 @@ export class Frontend extends EventEmitter {
143
289
  }
144
290
  if (this.initializeError)
145
291
  return;
292
+ // Create a WebSocket server and attach it to the http or https server
146
293
  const wssPort = this.port;
147
294
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
148
295
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
149
296
  this.webSocketServer.on('connection', (ws, request) => {
150
297
  const clientIp = request.socket.remoteAddress;
151
- let callbackLogLevel = "notice";
152
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
153
- callbackLogLevel = "info";
154
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
155
- callbackLogLevel = "debug";
298
+ // Set the global logger callback for the WebSocketServer
299
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
300
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
301
+ callbackLogLevel = "info" /* LogLevel.INFO */;
302
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
303
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
156
304
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
157
305
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
158
306
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -187,7 +335,7 @@ export class Frontend extends EventEmitter {
187
335
  this.webSocketServer.on('error', (ws, error) => {
188
336
  this.log.error(`WebSocketServer error: ${error}`);
189
337
  });
190
- const { cliEmitter } = await import('./cli.js');
338
+ // Subscribe to cli events
191
339
  cliEmitter.removeAllListeners();
192
340
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
193
341
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -198,6 +346,8 @@ export class Frontend extends EventEmitter {
198
346
  cliEmitter.on('cpu', (cpuUsage) => {
199
347
  this.wssSendCpuUpdate(cpuUsage);
200
348
  });
349
+ // Endpoint to validate login code
350
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
201
351
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
202
352
  const { password } = req.body;
203
353
  this.log.debug('The frontend sent /api/login', password);
@@ -216,23 +366,27 @@ export class Frontend extends EventEmitter {
216
366
  this.log.warn('/api/login error wrong password');
217
367
  res.json({ valid: false });
218
368
  }
369
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
219
370
  }
220
371
  catch (error) {
221
372
  this.log.error('/api/login error getting password');
222
373
  res.json({ valid: false });
223
374
  }
224
375
  });
376
+ // Endpoint to provide health check for docker
225
377
  this.expressApp.get('/health', (req, res) => {
226
378
  this.log.debug('Express received /health');
227
379
  const healthStatus = {
228
- status: 'ok',
229
- uptime: process.uptime(),
230
- timestamp: new Date().toISOString(),
380
+ status: 'ok', // Indicate service is healthy
381
+ uptime: process.uptime(), // Server uptime in seconds
382
+ timestamp: new Date().toISOString(), // Current timestamp
231
383
  };
232
384
  res.status(200).json(healthStatus);
233
385
  });
386
+ // Endpoint to provide memory usage details
234
387
  this.expressApp.get('/memory', async (req, res) => {
235
388
  this.log.debug('Express received /memory');
389
+ // Memory usage from process
236
390
  const memoryUsageRaw = process.memoryUsage();
237
391
  const memoryUsage = {
238
392
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -241,10 +395,13 @@ export class Frontend extends EventEmitter {
241
395
  external: this.formatMemoryUsage(memoryUsageRaw.external),
242
396
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
243
397
  };
398
+ // V8 heap statistics
244
399
  const { default: v8 } = await import('node:v8');
245
400
  const heapStatsRaw = v8.getHeapStatistics();
246
401
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
402
+ // Format heapStats
247
403
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
404
+ // Format heapSpaces
248
405
  const heapSpaces = heapSpacesRaw.map((space) => ({
249
406
  ...space,
250
407
  space_size: this.formatMemoryUsage(space.space_size),
@@ -262,19 +419,23 @@ export class Frontend extends EventEmitter {
262
419
  };
263
420
  res.status(200).json(memoryReport);
264
421
  });
422
+ // Endpoint to provide settings
265
423
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
266
424
  this.log.debug('The frontend sent /api/settings');
267
425
  res.json(await this.getApiSettings());
268
426
  });
427
+ // Endpoint to provide plugins
269
428
  this.expressApp.get('/api/plugins', async (req, res) => {
270
429
  this.log.debug('The frontend sent /api/plugins');
271
430
  res.json(this.getBaseRegisteredPlugins());
272
431
  });
432
+ // Endpoint to provide devices
273
433
  this.expressApp.get('/api/devices', async (req, res) => {
274
434
  this.log.debug('The frontend sent /api/devices');
275
435
  const devices = await this.getDevices();
276
436
  res.json(devices);
277
437
  });
438
+ // Endpoint to view the matterbridge log
278
439
  this.expressApp.get('/api/view-mblog', async (req, res) => {
279
440
  this.log.debug('The frontend sent /api/view-mblog');
280
441
  try {
@@ -287,6 +448,7 @@ export class Frontend extends EventEmitter {
287
448
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
288
449
  }
289
450
  });
451
+ // Endpoint to view the matter.js log
290
452
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
291
453
  this.log.debug('The frontend sent /api/view-mjlog');
292
454
  try {
@@ -299,6 +461,7 @@ export class Frontend extends EventEmitter {
299
461
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
300
462
  }
301
463
  });
464
+ // Endpoint to view the shelly log
302
465
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
303
466
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
304
467
  try {
@@ -311,9 +474,11 @@ export class Frontend extends EventEmitter {
311
474
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
312
475
  }
313
476
  });
477
+ // Endpoint to download the matterbridge log
314
478
  this.expressApp.get('/api/download-mblog', async (req, res) => {
315
479
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
316
480
  try {
481
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
317
482
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
318
483
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
319
484
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
@@ -323,16 +488,20 @@ export class Frontend extends EventEmitter {
323
488
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
324
489
  }
325
490
  res.type('text/plain');
491
+ // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
326
492
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
493
+ /* istanbul ignore if */
327
494
  if (error) {
328
495
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
329
496
  res.status(500).send('Error downloading the matterbridge log file');
330
497
  }
331
498
  });
332
499
  });
500
+ // Endpoint to download the matter log
333
501
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
334
502
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
335
503
  try {
504
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
336
505
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
337
506
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
338
507
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
@@ -343,15 +512,18 @@ export class Frontend extends EventEmitter {
343
512
  }
344
513
  res.type('text/plain');
345
514
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
515
+ /* istanbul ignore if */
346
516
  if (error) {
347
517
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
348
518
  res.status(500).send('Error downloading the matter log file');
349
519
  }
350
520
  });
351
521
  });
522
+ // Endpoint to download the shelly log
352
523
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
353
524
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
354
525
  try {
526
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
355
527
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
356
528
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
357
529
  await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
@@ -362,12 +534,14 @@ export class Frontend extends EventEmitter {
362
534
  }
363
535
  res.type('text/plain');
364
536
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
537
+ /* istanbul ignore if */
365
538
  if (error) {
366
539
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
367
540
  res.status(500).send('Error downloading Shelly system log file');
368
541
  }
369
542
  });
370
543
  });
544
+ // Endpoint to download the matterbridge storage directory
371
545
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
372
546
  this.log.debug('The frontend sent /api/download-mbstorage');
373
547
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -378,6 +552,7 @@ export class Frontend extends EventEmitter {
378
552
  }
379
553
  });
380
554
  });
555
+ // Endpoint to download the matter storage file
381
556
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
382
557
  this.log.debug('The frontend sent /api/download-mjstorage');
383
558
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -388,6 +563,7 @@ export class Frontend extends EventEmitter {
388
563
  }
389
564
  });
390
565
  });
566
+ // Endpoint to download the matterbridge plugin directory
391
567
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
392
568
  this.log.debug('The frontend sent /api/download-pluginstorage');
393
569
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -398,6 +574,7 @@ export class Frontend extends EventEmitter {
398
574
  }
399
575
  });
400
576
  });
577
+ // Endpoint to download the matterbridge plugin config files
401
578
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
402
579
  this.log.debug('The frontend sent /api/download-pluginconfig');
403
580
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
@@ -408,6 +585,7 @@ export class Frontend extends EventEmitter {
408
585
  }
409
586
  });
410
587
  });
588
+ // Endpoint to download the matterbridge backup (created with the backup command)
411
589
  this.expressApp.get('/api/download-backup', async (req, res) => {
412
590
  this.log.debug('The frontend sent /api/download-backup');
413
591
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -417,6 +595,7 @@ export class Frontend extends EventEmitter {
417
595
  }
418
596
  });
419
597
  });
598
+ // Endpoint to upload a package
420
599
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
421
600
  const { filename } = req.body;
422
601
  const file = req.file;
@@ -426,10 +605,13 @@ export class Frontend extends EventEmitter {
426
605
  return;
427
606
  }
428
607
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
608
+ // Define the path where the plugin file will be saved
429
609
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
430
610
  try {
611
+ // Move the uploaded file to the specified path
431
612
  await fs.rename(file.path, filePath);
432
613
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
614
+ // Install the plugin package
433
615
  if (filename.endsWith('.tgz')) {
434
616
  const { spawnCommand } = await import('./utils/spawn.js');
435
617
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
@@ -449,6 +631,7 @@ export class Frontend extends EventEmitter {
449
631
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
450
632
  }
451
633
  });
634
+ // Fallback for routing (must be the last route)
452
635
  this.expressApp.use((req, res) => {
453
636
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
454
637
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -457,12 +640,15 @@ export class Frontend extends EventEmitter {
457
640
  }
458
641
  async stop() {
459
642
  this.log.debug('Stopping the frontend...');
643
+ // Remove listeners from the express app
460
644
  if (this.expressApp) {
461
645
  this.expressApp.removeAllListeners();
462
646
  this.expressApp = undefined;
463
647
  this.log.debug('Frontend app closed successfully');
464
648
  }
649
+ // Close the WebSocket server
465
650
  if (this.webSocketServer) {
651
+ // Close all active connections
466
652
  this.webSocketServer.clients.forEach((client) => {
467
653
  if (client.readyState === WebSocket.OPEN) {
468
654
  client.close();
@@ -482,6 +668,7 @@ export class Frontend extends EventEmitter {
482
668
  this.webSocketServer.removeAllListeners();
483
669
  this.webSocketServer = undefined;
484
670
  }
671
+ // Close the http server
485
672
  if (this.httpServer) {
486
673
  await withTimeout(new Promise((resolve) => {
487
674
  this.httpServer?.close((error) => {
@@ -498,6 +685,7 @@ export class Frontend extends EventEmitter {
498
685
  this.httpServer = undefined;
499
686
  this.log.debug('Frontend http server closed successfully');
500
687
  }
688
+ // Close the https server
501
689
  if (this.httpsServer) {
502
690
  await withTimeout(new Promise((resolve) => {
503
691
  this.httpsServer?.close((error) => {
@@ -516,6 +704,7 @@ export class Frontend extends EventEmitter {
516
704
  }
517
705
  this.log.debug('Frontend stopped successfully');
518
706
  }
707
+ // Function to format bytes to KB, MB, or GB
519
708
  formatMemoryUsage = (bytes) => {
520
709
  if (bytes >= 1024 ** 3) {
521
710
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -527,6 +716,7 @@ export class Frontend extends EventEmitter {
527
716
  return `${(bytes / 1024).toFixed(2)} KB`;
528
717
  }
529
718
  };
719
+ // Function to format system uptime with only the most significant unit
530
720
  formatOsUpTime = (seconds) => {
531
721
  if (seconds >= 86400) {
532
722
  const days = Math.floor(seconds / 86400);
@@ -542,8 +732,13 @@ export class Frontend extends EventEmitter {
542
732
  }
543
733
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
544
734
  };
735
+ /**
736
+ * Retrieves the api settings data.
737
+ *
738
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
739
+ */
545
740
  async getApiSettings() {
546
- const { lastCpuUsage } = await import('./cli.js');
741
+ // Update the system information
547
742
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
548
743
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
549
744
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -552,6 +747,7 @@ export class Frontend extends EventEmitter {
552
747
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
553
748
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
554
749
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
750
+ // Update the matterbridge information
555
751
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
556
752
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
557
753
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -570,6 +766,12 @@ export class Frontend extends EventEmitter {
570
766
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
571
767
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
572
768
  }
769
+ /**
770
+ * Retrieves the reachable attribute.
771
+ *
772
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
773
+ * @returns {boolean} The reachable attribute.
774
+ */
573
775
  getReachability(device) {
574
776
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
575
777
  return false;
@@ -581,6 +783,12 @@ export class Frontend extends EventEmitter {
581
783
  return true;
582
784
  return false;
583
785
  }
786
+ /**
787
+ * Retrieves the power source attribute.
788
+ *
789
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
790
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
791
+ */
584
792
  getPowerSource(endpoint) {
585
793
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
586
794
  return undefined;
@@ -596,13 +804,21 @@ export class Frontend extends EventEmitter {
596
804
  }
597
805
  return;
598
806
  };
807
+ // Root endpoint
599
808
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
600
809
  return powerSource(endpoint);
810
+ // Child endpoints
601
811
  for (const child of endpoint.getChildEndpoints()) {
602
812
  if (child.hasClusterServer(PowerSource.Cluster.id))
603
813
  return powerSource(child);
604
814
  }
605
815
  }
816
+ /**
817
+ * Retrieves the matter pairing code from a given device.
818
+ *
819
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object to retrieve the QR pairing code from.
820
+ * @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
821
+ */
606
822
  getMatterDataFromDevice(device) {
607
823
  if (device.mode === 'server' && device.serverNode && device.serverContext) {
608
824
  return {
@@ -614,6 +830,12 @@ export class Frontend extends EventEmitter {
614
830
  };
615
831
  }
616
832
  }
833
+ /**
834
+ * Retrieves the cluster text description from a given device.
835
+ *
836
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
837
+ * @returns {string} The attributes description of the cluster servers in the device.
838
+ */
617
839
  getClusterTextFromDevice(device) {
618
840
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
619
841
  return '';
@@ -654,7 +876,19 @@ export class Frontend extends EventEmitter {
654
876
  };
655
877
  let attributes = '';
656
878
  let supportedModes = [];
879
+ /*
880
+ Object.keys(device.behaviors.supported).forEach((clusterName) => {
881
+ const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
882
+ // console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
883
+ if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
884
+ Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
885
+ // console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
886
+ });
887
+ }
888
+ });
889
+ */
657
890
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
891
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
658
892
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
659
893
  return;
660
894
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -746,8 +980,14 @@ export class Frontend extends EventEmitter {
746
980
  if (clusterName === 'userLabel' && attributeName === 'labelList')
747
981
  attributes += `${getUserLabel(device)} `;
748
982
  });
983
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
749
984
  return attributes.trimStart().trimEnd();
750
985
  }
986
+ /**
987
+ * Retrieves the base registered plugins sanitized for res.json().
988
+ *
989
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
990
+ */
751
991
  getBaseRegisteredPlugins() {
752
992
  const baseRegisteredPlugins = [];
753
993
  for (const plugin of this.matterbridge.plugins) {
@@ -786,11 +1026,19 @@ export class Frontend extends EventEmitter {
786
1026
  }
787
1027
  return baseRegisteredPlugins;
788
1028
  }
1029
+ /**
1030
+ * Retrieves the devices from Matterbridge.
1031
+ *
1032
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1033
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1034
+ */
789
1035
  async getDevices(pluginName) {
790
1036
  const devices = [];
791
1037
  for (const device of this.matterbridge.devices.array()) {
1038
+ // Filter by pluginName if provided
792
1039
  if (pluginName && pluginName !== device.plugin)
793
1040
  continue;
1041
+ // Check if the device has the required properties
794
1042
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
795
1043
  continue;
796
1044
  devices.push({
@@ -810,22 +1058,37 @@ export class Frontend extends EventEmitter {
810
1058
  }
811
1059
  return devices;
812
1060
  }
1061
+ /**
1062
+ * Retrieves the clusters from a given plugin and endpoint number.
1063
+ *
1064
+ * Response for /api/clusters
1065
+ *
1066
+ * @param {string} pluginName - The name of the plugin.
1067
+ * @param {number} endpointNumber - The endpoint number.
1068
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1069
+ */
813
1070
  getClusters(pluginName, endpointNumber) {
814
1071
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
815
1072
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
816
1073
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
817
1074
  return;
818
1075
  }
1076
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1077
+ // Get the device types from the main endpoint
819
1078
  const deviceTypes = [];
820
1079
  const clusters = [];
821
1080
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
822
1081
  deviceTypes.push(d.deviceType);
823
1082
  });
1083
+ // Get the clusters from the main endpoint
824
1084
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
825
1085
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
826
1086
  return;
827
1087
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
828
1088
  return;
1089
+ // console.log(
1090
+ // `${idn}${endpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
1091
+ // );
829
1092
  clusters.push({
830
1093
  endpoint: endpoint.number.toString(),
831
1094
  id: 'main',
@@ -838,12 +1101,18 @@ export class Frontend extends EventEmitter {
838
1101
  attributeLocalValue: attributeValue,
839
1102
  });
840
1103
  });
1104
+ // Get the child endpoints
841
1105
  const childEndpoints = endpoint.getChildEndpoints();
1106
+ // if (childEndpoints.length === 0) {
1107
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1108
+ // }
842
1109
  childEndpoints.forEach((childEndpoint) => {
843
1110
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
844
1111
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
845
1112
  return;
846
1113
  }
1114
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1115
+ // Get the device types of the child endpoint
847
1116
  const deviceTypes = [];
848
1117
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
849
1118
  deviceTypes.push(d.deviceType);
@@ -853,9 +1122,12 @@ export class Frontend extends EventEmitter {
853
1122
  return;
854
1123
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
855
1124
  return;
1125
+ // console.log(
1126
+ // `${idn}${childEndpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
1127
+ // );
856
1128
  clusters.push({
857
1129
  endpoint: childEndpoint.number.toString(),
858
- id: childEndpoint.maybeId ?? 'null',
1130
+ id: childEndpoint.maybeId ?? 'null', // Never happens
859
1131
  deviceTypes,
860
1132
  clusterName: capitalizeFirstLetter(clusterName),
861
1133
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -868,6 +1140,13 @@ export class Frontend extends EventEmitter {
868
1140
  });
869
1141
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
870
1142
  }
1143
+ /**
1144
+ * Handles incoming websocket messages for the Matterbridge frontend.
1145
+ *
1146
+ * @param {WebSocket} client - The websocket client that sent the message.
1147
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1148
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1149
+ */
871
1150
  async wsMessageHandler(client, message) {
872
1151
  let data;
873
1152
  try {
@@ -914,32 +1193,41 @@ export class Frontend extends EventEmitter {
914
1193
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
915
1194
  const packageName = data.params.packageName.replace(/@.*$/, '');
916
1195
  if (data.params.restart === false && packageName !== 'matterbridge') {
1196
+ // The install comes from InstallPlugins
917
1197
  this.matterbridge.plugins
918
1198
  .add(packageName)
919
1199
  .then((plugin) => {
920
1200
  if (plugin) {
1201
+ // The plugin is not registered
921
1202
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
922
1203
  this.matterbridge.plugins
923
1204
  .load(plugin, true, 'The plugin has been added', true)
1205
+ // eslint-disable-next-line promise/no-nesting
924
1206
  .then(() => {
925
1207
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
926
1208
  this.wssSendRefreshRequired('plugins');
927
1209
  return;
928
1210
  })
1211
+ // eslint-disable-next-line promise/no-nesting
929
1212
  .catch((_error) => {
1213
+ //
930
1214
  });
931
1215
  }
932
1216
  else {
1217
+ // The plugin is already registered
933
1218
  this.wssSendSnackbarMessage(`Restart required`, 0);
934
1219
  this.wssSendRefreshRequired('plugins');
935
1220
  this.wssSendRestartRequired();
936
1221
  }
937
1222
  return;
938
1223
  })
1224
+ // eslint-disable-next-line promise/no-nesting
939
1225
  .catch((_error) => {
1226
+ //
940
1227
  });
941
1228
  }
942
1229
  else {
1230
+ // The package is matterbridge
943
1231
  if (this.matterbridge.restartMode !== '') {
944
1232
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
945
1233
  this.matterbridge.shutdownProcess();
@@ -962,6 +1250,7 @@ export class Frontend extends EventEmitter {
962
1250
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
963
1251
  return;
964
1252
  }
1253
+ // The package is a plugin
965
1254
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
966
1255
  if (plugin) {
967
1256
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -970,6 +1259,7 @@ export class Frontend extends EventEmitter {
970
1259
  this.wssSendRefreshRequired('plugins');
971
1260
  this.wssSendRefreshRequired('devices');
972
1261
  }
1262
+ // Uninstall the package
973
1263
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
974
1264
  const { spawnCommand } = await import('./utils/spawn.js');
975
1265
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1010,6 +1300,7 @@ export class Frontend extends EventEmitter {
1010
1300
  return;
1011
1301
  })
1012
1302
  .catch((_error) => {
1303
+ //
1013
1304
  });
1014
1305
  }
1015
1306
  else {
@@ -1056,6 +1347,7 @@ export class Frontend extends EventEmitter {
1056
1347
  return;
1057
1348
  })
1058
1349
  .catch((_error) => {
1350
+ //
1059
1351
  });
1060
1352
  }
1061
1353
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1293,22 +1585,22 @@ export class Frontend extends EventEmitter {
1293
1585
  if (isValidString(data.params.value, 4)) {
1294
1586
  this.log.debug('Matterbridge logger level:', data.params.value);
1295
1587
  if (data.params.value === 'Debug') {
1296
- await this.matterbridge.setLogLevel("debug");
1588
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1297
1589
  }
1298
1590
  else if (data.params.value === 'Info') {
1299
- await this.matterbridge.setLogLevel("info");
1591
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1300
1592
  }
1301
1593
  else if (data.params.value === 'Notice') {
1302
- await this.matterbridge.setLogLevel("notice");
1594
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1303
1595
  }
1304
1596
  else if (data.params.value === 'Warn') {
1305
- await this.matterbridge.setLogLevel("warn");
1597
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1306
1598
  }
1307
1599
  else if (data.params.value === 'Error') {
1308
- await this.matterbridge.setLogLevel("error");
1600
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1309
1601
  }
1310
1602
  else if (data.params.value === 'Fatal') {
1311
- await this.matterbridge.setLogLevel("fatal");
1603
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1312
1604
  }
1313
1605
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1314
1606
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1319,6 +1611,7 @@ export class Frontend extends EventEmitter {
1319
1611
  this.log.debug('Matterbridge file log:', data.params.value);
1320
1612
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1321
1613
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1614
+ // Create the file logger for matterbridge
1322
1615
  if (data.params.value)
1323
1616
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1324
1617
  else
@@ -1483,15 +1776,19 @@ export class Frontend extends EventEmitter {
1483
1776
  return;
1484
1777
  }
1485
1778
  const config = plugin.configJson;
1779
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1486
1780
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1781
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1487
1782
  if (select === 'serial')
1488
1783
  this.log.info(`Selected device serial ${data.params.serial}`);
1489
1784
  if (select === 'name')
1490
1785
  this.log.info(`Selected device name ${data.params.name}`);
1491
1786
  if (config && select && (select === 'serial' || select === 'name')) {
1787
+ // Remove postfix from the serial if it exists
1492
1788
  if (config.postfix) {
1493
1789
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1494
1790
  }
1791
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1495
1792
  if (isValidArray(config.whiteList, 1)) {
1496
1793
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1497
1794
  config.whiteList.push(data.params.serial);
@@ -1500,6 +1797,7 @@ export class Frontend extends EventEmitter {
1500
1797
  config.whiteList.push(data.params.name);
1501
1798
  }
1502
1799
  }
1800
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1503
1801
  if (isValidArray(config.blackList, 1)) {
1504
1802
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1505
1803
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1529,7 +1827,9 @@ export class Frontend extends EventEmitter {
1529
1827
  return;
1530
1828
  }
1531
1829
  const config = plugin.configJson;
1830
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1532
1831
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1832
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1533
1833
  if (select === 'serial')
1534
1834
  this.log.info(`Unselected device serial ${data.params.serial}`);
1535
1835
  if (select === 'name')
@@ -1538,6 +1838,7 @@ export class Frontend extends EventEmitter {
1538
1838
  if (config.postfix) {
1539
1839
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1540
1840
  }
1841
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1541
1842
  if (isValidArray(config.whiteList, 1)) {
1542
1843
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1543
1844
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1546,6 +1847,7 @@ export class Frontend extends EventEmitter {
1546
1847
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1547
1848
  }
1548
1849
  }
1850
+ // Add the serial to the blackList
1549
1851
  if (isValidArray(config.blackList)) {
1550
1852
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1551
1853
  config.blackList.push(data.params.serial);
@@ -1578,114 +1880,230 @@ export class Frontend extends EventEmitter {
1578
1880
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1579
1881
  }
1580
1882
  }
1883
+ /**
1884
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1885
+ *
1886
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1887
+ * @param {string} time - The time string of the message
1888
+ * @param {string} name - The logger name of the message
1889
+ * @param {string} message - The content of the message.
1890
+ *
1891
+ * @remarks
1892
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1893
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1894
+ * The function sends the message to all connected clients.
1895
+ */
1581
1896
  wssSendMessage(level, time, name, message) {
1582
1897
  if (!level || !time || !name || !message)
1583
1898
  return;
1899
+ // Remove ANSI escape codes from the message
1900
+ // eslint-disable-next-line no-control-regex
1584
1901
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1902
+ // Remove leading asterisks from the message
1585
1903
  message = message.replace(/^\*+/, '');
1904
+ // Replace all occurrences of \t and \n
1586
1905
  message = message.replace(/[\t\n]/g, '');
1906
+ // Remove non-printable characters
1907
+ // eslint-disable-next-line no-control-regex
1587
1908
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1909
+ // Replace all occurrences of \" with "
1588
1910
  message = message.replace(/\\"/g, '"');
1911
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1589
1912
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1913
+ // Define the maximum allowed length for continuous characters without a space
1590
1914
  const maxContinuousLength = 100;
1591
1915
  const keepStartLength = 20;
1592
1916
  const keepEndLength = 20;
1917
+ // Split the message into words
1593
1918
  message = message
1594
1919
  .split(' ')
1595
1920
  .map((word) => {
1921
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1596
1922
  if (word.length > maxContinuousLength) {
1597
1923
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1598
1924
  }
1599
1925
  return word;
1600
1926
  })
1601
1927
  .join(' ');
1928
+ // Send the message to all connected clients
1602
1929
  this.webSocketServer?.clients.forEach((client) => {
1603
1930
  if (client.readyState === WebSocket.OPEN) {
1604
1931
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1605
1932
  }
1606
1933
  });
1607
1934
  }
1935
+ /**
1936
+ * Sends a need to refresh WebSocket message to all connected clients.
1937
+ *
1938
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1939
+ * possible values:
1940
+ * - 'matterbridgeLatestVersion'
1941
+ * - 'matterbridgeAdvertise'
1942
+ * - 'online'
1943
+ * - 'offline'
1944
+ * - 'reachability'
1945
+ * - 'settings'
1946
+ * - 'plugins'
1947
+ * - 'pluginsRestart'
1948
+ * - 'devices'
1949
+ * - 'fabrics'
1950
+ * - 'sessions'
1951
+ */
1608
1952
  wssSendRefreshRequired(changed = null) {
1609
1953
  this.log.debug('Sending a refresh required message to all connected clients');
1954
+ // Send the message to all connected clients
1610
1955
  this.webSocketServer?.clients.forEach((client) => {
1611
1956
  if (client.readyState === WebSocket.OPEN) {
1612
1957
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1613
1958
  }
1614
1959
  });
1615
1960
  }
1961
+ /**
1962
+ * Sends a need to restart WebSocket message to all connected clients.
1963
+ *
1964
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
1965
+ */
1616
1966
  wssSendRestartRequired(snackbar = true) {
1617
1967
  this.log.debug('Sending a restart required message to all connected clients');
1618
1968
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1619
1969
  if (snackbar === true)
1620
1970
  this.wssSendSnackbarMessage(`Restart required`, 0);
1971
+ // Send the message to all connected clients
1621
1972
  this.webSocketServer?.clients.forEach((client) => {
1622
1973
  if (client.readyState === WebSocket.OPEN) {
1623
1974
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1624
1975
  }
1625
1976
  });
1626
1977
  }
1978
+ /**
1979
+ * Sends a need to update WebSocket message to all connected clients.
1980
+ *
1981
+ */
1627
1982
  wssSendUpdateRequired() {
1628
1983
  this.log.debug('Sending an update required message to all connected clients');
1629
1984
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1985
+ // Send the message to all connected clients
1630
1986
  this.webSocketServer?.clients.forEach((client) => {
1631
1987
  if (client.readyState === WebSocket.OPEN) {
1632
1988
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1633
1989
  }
1634
1990
  });
1635
1991
  }
1992
+ /**
1993
+ * Sends a cpu update message to all connected clients.
1994
+ *
1995
+ * @param {number} cpuUsage - The CPU usage percentage to send.
1996
+ */
1636
1997
  wssSendCpuUpdate(cpuUsage) {
1637
1998
  if (hasParameter('debug'))
1638
1999
  this.log.debug('Sending a cpu update message to all connected clients');
2000
+ // Send the message to all connected clients
1639
2001
  this.webSocketServer?.clients.forEach((client) => {
1640
2002
  if (client.readyState === WebSocket.OPEN) {
1641
2003
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1642
2004
  }
1643
2005
  });
1644
2006
  }
2007
+ /**
2008
+ * Sends a memory update message to all connected clients.
2009
+ *
2010
+ * @param {string} totalMemory - The total memory in bytes.
2011
+ * @param {string} freeMemory - The free memory in bytes.
2012
+ * @param {string} rss - The resident set size in bytes.
2013
+ * @param {string} heapTotal - The total heap memory in bytes.
2014
+ * @param {string} heapUsed - The used heap memory in bytes.
2015
+ * @param {string} external - The external memory in bytes.
2016
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2017
+ */
1645
2018
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1646
2019
  if (hasParameter('debug'))
1647
2020
  this.log.debug('Sending a memory update message to all connected clients');
2021
+ // Send the message to all connected clients
1648
2022
  this.webSocketServer?.clients.forEach((client) => {
1649
2023
  if (client.readyState === WebSocket.OPEN) {
1650
2024
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1651
2025
  }
1652
2026
  });
1653
2027
  }
2028
+ /**
2029
+ * Sends an uptime update message to all connected clients.
2030
+ *
2031
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2032
+ * @param {string} processUptime - The process uptime in a human-readable format.
2033
+ */
1654
2034
  wssSendUptimeUpdate(systemUptime, processUptime) {
1655
2035
  if (hasParameter('debug'))
1656
2036
  this.log.debug('Sending a uptime update message to all connected clients');
2037
+ // Send the message to all connected clients
1657
2038
  this.webSocketServer?.clients.forEach((client) => {
1658
2039
  if (client.readyState === WebSocket.OPEN) {
1659
2040
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1660
2041
  }
1661
2042
  });
1662
2043
  }
2044
+ /**
2045
+ * Sends an open snackbar message to all connected clients.
2046
+ *
2047
+ * @param {string} message - The message to send.
2048
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
2049
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2050
+ */
1663
2051
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1664
2052
  this.log.debug('Sending a snackbar message to all connected clients');
2053
+ // Send the message to all connected clients
1665
2054
  this.webSocketServer?.clients.forEach((client) => {
1666
2055
  if (client.readyState === WebSocket.OPEN) {
1667
2056
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1668
2057
  }
1669
2058
  });
1670
2059
  }
2060
+ /**
2061
+ * Sends a close snackbar message to all connected clients.
2062
+ *
2063
+ * @param {string} message - The message to send.
2064
+ */
1671
2065
  wssSendCloseSnackbarMessage(message) {
1672
2066
  this.log.debug('Sending a close snackbar message to all connected clients');
2067
+ // Send the message to all connected clients
1673
2068
  this.webSocketServer?.clients.forEach((client) => {
1674
2069
  if (client.readyState === WebSocket.OPEN) {
1675
2070
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1676
2071
  }
1677
2072
  });
1678
2073
  }
2074
+ /**
2075
+ * Sends an attribute update message to all connected WebSocket clients.
2076
+ *
2077
+ * @param {string | undefined} plugin - The name of the plugin.
2078
+ * @param {string | undefined} serialNumber - The serial number of the device.
2079
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2080
+ * @param {string} cluster - The cluster name where the attribute belongs.
2081
+ * @param {string} attribute - The name of the attribute that changed.
2082
+ * @param {number | string | boolean} value - The new value of the attribute.
2083
+ *
2084
+ * @remarks
2085
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2086
+ * with the updated attribute information.
2087
+ */
1679
2088
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1680
2089
  this.log.debug('Sending an attribute update message to all connected clients');
2090
+ // Send the message to all connected clients
1681
2091
  this.webSocketServer?.clients.forEach((client) => {
1682
2092
  if (client.readyState === WebSocket.OPEN) {
1683
2093
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1684
2094
  }
1685
2095
  });
1686
2096
  }
2097
+ /**
2098
+ * Sends a message to all connected clients.
2099
+ *
2100
+ * @param {number} id - The message id.
2101
+ * @param {string} method - The message method.
2102
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
2103
+ */
1687
2104
  wssBroadcastMessage(id, method, params) {
1688
2105
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2106
+ // Send the message to all connected clients
1689
2107
  this.webSocketServer?.clients.forEach((client) => {
1690
2108
  if (client.readyState === WebSocket.OPEN) {
1691
2109
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1693,3 +2111,4 @@ export class Frontend extends EventEmitter {
1693
2111
  });
1694
2112
  }
1695
2113
  }
2114
+ //# sourceMappingURL=frontend.js.map