matterbridge 3.1.2-dev-20250708-167e3ae → 3.1.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 (207) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/dist/cli.d.ts +26 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +91 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cliEmitter.d.ts +34 -0
  7. package/dist/cliEmitter.d.ts.map +1 -0
  8. package/dist/cliEmitter.js +30 -0
  9. package/dist/cliEmitter.js.map +1 -0
  10. package/dist/clusters/export.d.ts +2 -0
  11. package/dist/clusters/export.d.ts.map +1 -0
  12. package/dist/clusters/export.js +2 -0
  13. package/dist/clusters/export.js.map +1 -0
  14. package/dist/defaultConfigSchema.d.ts +28 -0
  15. package/dist/defaultConfigSchema.d.ts.map +1 -0
  16. package/dist/defaultConfigSchema.js +24 -0
  17. package/dist/defaultConfigSchema.js.map +1 -0
  18. package/dist/deviceManager.d.ts +112 -0
  19. package/dist/deviceManager.d.ts.map +1 -0
  20. package/dist/deviceManager.js +94 -1
  21. package/dist/deviceManager.js.map +1 -0
  22. package/dist/devices/batteryStorage.d.ts +48 -0
  23. package/dist/devices/batteryStorage.d.ts.map +1 -0
  24. package/dist/devices/batteryStorage.js +48 -1
  25. package/dist/devices/batteryStorage.js.map +1 -0
  26. package/dist/devices/evse.d.ts +75 -0
  27. package/dist/devices/evse.d.ts.map +1 -0
  28. package/dist/devices/evse.js +74 -10
  29. package/dist/devices/evse.js.map +1 -0
  30. package/dist/devices/export.d.ts +9 -0
  31. package/dist/devices/export.d.ts.map +1 -0
  32. package/dist/devices/export.js +2 -0
  33. package/dist/devices/export.js.map +1 -0
  34. package/dist/devices/heatPump.d.ts +47 -0
  35. package/dist/devices/heatPump.d.ts.map +1 -0
  36. package/dist/devices/heatPump.js +50 -2
  37. package/dist/devices/heatPump.js.map +1 -0
  38. package/dist/devices/laundryDryer.d.ts +87 -0
  39. package/dist/devices/laundryDryer.d.ts.map +1 -0
  40. package/dist/devices/laundryDryer.js +83 -6
  41. package/dist/devices/laundryDryer.js.map +1 -0
  42. package/dist/devices/laundryWasher.d.ts +242 -0
  43. package/dist/devices/laundryWasher.d.ts.map +1 -0
  44. package/dist/devices/laundryWasher.js +91 -7
  45. package/dist/devices/laundryWasher.js.map +1 -0
  46. package/dist/devices/roboticVacuumCleaner.d.ts +110 -0
  47. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  48. package/dist/devices/roboticVacuumCleaner.js +89 -6
  49. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  50. package/dist/devices/solarPower.d.ts +40 -0
  51. package/dist/devices/solarPower.d.ts.map +1 -0
  52. package/dist/devices/solarPower.js +38 -0
  53. package/dist/devices/solarPower.js.map +1 -0
  54. package/dist/devices/waterHeater.d.ts +111 -0
  55. package/dist/devices/waterHeater.d.ts.map +1 -0
  56. package/dist/devices/waterHeater.js +82 -2
  57. package/dist/devices/waterHeater.js.map +1 -0
  58. package/dist/frontend.d.ts +303 -0
  59. package/dist/frontend.d.ts.map +1 -0
  60. package/dist/frontend.js +417 -16
  61. package/dist/frontend.js.map +1 -0
  62. package/dist/globalMatterbridge.d.ts +59 -0
  63. package/dist/globalMatterbridge.d.ts.map +1 -0
  64. package/dist/globalMatterbridge.js +47 -0
  65. package/dist/globalMatterbridge.js.map +1 -0
  66. package/dist/helpers.d.ts +48 -0
  67. package/dist/helpers.d.ts.map +1 -0
  68. package/dist/helpers.js +53 -0
  69. package/dist/helpers.js.map +1 -0
  70. package/dist/index.d.ts +33 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +30 -1
  73. package/dist/index.js.map +1 -0
  74. package/dist/logger/export.d.ts +2 -0
  75. package/dist/logger/export.d.ts.map +1 -0
  76. package/dist/logger/export.js +1 -0
  77. package/dist/logger/export.js.map +1 -0
  78. package/dist/matter/behaviors.d.ts +2 -0
  79. package/dist/matter/behaviors.d.ts.map +1 -0
  80. package/dist/matter/behaviors.js +2 -0
  81. package/dist/matter/behaviors.js.map +1 -0
  82. package/dist/matter/clusters.d.ts +2 -0
  83. package/dist/matter/clusters.d.ts.map +1 -0
  84. package/dist/matter/clusters.js +2 -0
  85. package/dist/matter/clusters.js.map +1 -0
  86. package/dist/matter/devices.d.ts +2 -0
  87. package/dist/matter/devices.d.ts.map +1 -0
  88. package/dist/matter/devices.js +2 -0
  89. package/dist/matter/devices.js.map +1 -0
  90. package/dist/matter/endpoints.d.ts +2 -0
  91. package/dist/matter/endpoints.d.ts.map +1 -0
  92. package/dist/matter/endpoints.js +2 -0
  93. package/dist/matter/endpoints.js.map +1 -0
  94. package/dist/matter/export.d.ts +5 -0
  95. package/dist/matter/export.d.ts.map +1 -0
  96. package/dist/matter/export.js +3 -0
  97. package/dist/matter/export.js.map +1 -0
  98. package/dist/matter/types.d.ts +3 -0
  99. package/dist/matter/types.d.ts.map +1 -0
  100. package/dist/matter/types.js +3 -0
  101. package/dist/matter/types.js.map +1 -0
  102. package/dist/matterbridge.d.ts +450 -0
  103. package/dist/matterbridge.d.ts.map +1 -0
  104. package/dist/matterbridge.js +803 -54
  105. package/dist/matterbridge.js.map +1 -0
  106. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  107. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  108. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  109. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  110. package/dist/matterbridgeBehaviors.d.ts +1340 -0
  111. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  112. package/dist/matterbridgeBehaviors.js +61 -1
  113. package/dist/matterbridgeBehaviors.js.map +1 -0
  114. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  115. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  116. package/dist/matterbridgeDeviceTypes.js +579 -15
  117. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  118. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  119. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  120. package/dist/matterbridgeDynamicPlatform.js +36 -0
  121. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  122. package/dist/matterbridgeEndpoint.d.ts +1196 -0
  123. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  124. package/dist/matterbridgeEndpoint.js +1053 -42
  125. package/dist/matterbridgeEndpoint.js.map +1 -0
  126. package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
  127. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  128. package/dist/matterbridgeEndpointHelpers.js +322 -12
  129. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  130. package/dist/matterbridgePlatform.d.ts +310 -0
  131. package/dist/matterbridgePlatform.d.ts.map +1 -0
  132. package/dist/matterbridgePlatform.js +233 -0
  133. package/dist/matterbridgePlatform.js.map +1 -0
  134. package/dist/matterbridgeTypes.d.ts +192 -0
  135. package/dist/matterbridgeTypes.d.ts.map +1 -0
  136. package/dist/matterbridgeTypes.js +25 -0
  137. package/dist/matterbridgeTypes.js.map +1 -0
  138. package/dist/pluginManager.d.ts +291 -0
  139. package/dist/pluginManager.d.ts.map +1 -0
  140. package/dist/pluginManager.js +269 -3
  141. package/dist/pluginManager.js.map +1 -0
  142. package/dist/shelly.d.ts +174 -0
  143. package/dist/shelly.d.ts.map +1 -0
  144. package/dist/shelly.js +168 -7
  145. package/dist/shelly.js.map +1 -0
  146. package/dist/storage/export.d.ts +2 -0
  147. package/dist/storage/export.d.ts.map +1 -0
  148. package/dist/storage/export.js +1 -0
  149. package/dist/storage/export.js.map +1 -0
  150. package/dist/update.d.ts +59 -0
  151. package/dist/update.d.ts.map +1 -0
  152. package/dist/update.js +54 -0
  153. package/dist/update.js.map +1 -0
  154. package/dist/utils/colorUtils.d.ts +117 -0
  155. package/dist/utils/colorUtils.d.ts.map +1 -0
  156. package/dist/utils/colorUtils.js +263 -2
  157. package/dist/utils/colorUtils.js.map +1 -0
  158. package/dist/utils/commandLine.d.ts +59 -0
  159. package/dist/utils/commandLine.d.ts.map +1 -0
  160. package/dist/utils/commandLine.js +54 -0
  161. package/dist/utils/commandLine.js.map +1 -0
  162. package/dist/utils/copyDirectory.d.ts +33 -0
  163. package/dist/utils/copyDirectory.d.ts.map +1 -0
  164. package/dist/utils/copyDirectory.js +38 -1
  165. package/dist/utils/copyDirectory.js.map +1 -0
  166. package/dist/utils/createDirectory.d.ts +34 -0
  167. package/dist/utils/createDirectory.d.ts.map +1 -0
  168. package/dist/utils/createDirectory.js +33 -0
  169. package/dist/utils/createDirectory.js.map +1 -0
  170. package/dist/utils/createZip.d.ts +39 -0
  171. package/dist/utils/createZip.d.ts.map +1 -0
  172. package/dist/utils/createZip.js +47 -2
  173. package/dist/utils/createZip.js.map +1 -0
  174. package/dist/utils/deepCopy.d.ts +32 -0
  175. package/dist/utils/deepCopy.d.ts.map +1 -0
  176. package/dist/utils/deepCopy.js +39 -0
  177. package/dist/utils/deepCopy.js.map +1 -0
  178. package/dist/utils/deepEqual.d.ts +54 -0
  179. package/dist/utils/deepEqual.d.ts.map +1 -0
  180. package/dist/utils/deepEqual.js +72 -1
  181. package/dist/utils/deepEqual.js.map +1 -0
  182. package/dist/utils/export.d.ts +12 -0
  183. package/dist/utils/export.d.ts.map +1 -0
  184. package/dist/utils/export.js +1 -0
  185. package/dist/utils/export.js.map +1 -0
  186. package/dist/utils/hex.d.ts +49 -0
  187. package/dist/utils/hex.d.ts.map +1 -0
  188. package/dist/utils/hex.js +58 -0
  189. package/dist/utils/hex.js.map +1 -0
  190. package/dist/utils/isvalid.d.ts +103 -0
  191. package/dist/utils/isvalid.d.ts.map +1 -0
  192. package/dist/utils/isvalid.js +101 -0
  193. package/dist/utils/isvalid.js.map +1 -0
  194. package/dist/utils/network.d.ts +76 -0
  195. package/dist/utils/network.d.ts.map +1 -0
  196. package/dist/utils/network.js +83 -5
  197. package/dist/utils/network.js.map +1 -0
  198. package/dist/utils/spawn.d.ts +11 -0
  199. package/dist/utils/spawn.d.ts.map +1 -0
  200. package/dist/utils/spawn.js +18 -0
  201. package/dist/utils/spawn.js.map +1 -0
  202. package/dist/utils/wait.d.ts +56 -0
  203. package/dist/utils/wait.d.ts.map +1 -0
  204. package/dist/utils/wait.js +62 -9
  205. package/dist/utils/wait.js.map +1 -0
  206. package/npm-shrinkwrap.json +2 -2
  207. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,30 +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, 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, getAttribute } from './matterbridgeEndpointHelpers.js';
16
44
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
45
+ /**
46
+ * Websocket message ID for logging.
47
+ *
48
+ * @constant {number}
49
+ */
17
50
  export const WS_ID_LOG = 0;
51
+ /**
52
+ * Websocket message ID indicating a refresh is needed.
53
+ *
54
+ * @constant {number}
55
+ */
18
56
  export const WS_ID_REFRESH_NEEDED = 1;
57
+ /**
58
+ * Websocket message ID indicating a restart is needed.
59
+ *
60
+ * @constant {number}
61
+ */
19
62
  export const WS_ID_RESTART_NEEDED = 2;
63
+ /**
64
+ * Websocket message ID indicating a cpu update.
65
+ *
66
+ * @constant {number}
67
+ */
20
68
  export const WS_ID_CPU_UPDATE = 3;
69
+ /**
70
+ * Websocket message ID indicating a memory update.
71
+ *
72
+ * @constant {number}
73
+ */
21
74
  export const WS_ID_MEMORY_UPDATE = 4;
75
+ /**
76
+ * Websocket message ID indicating an uptime update.
77
+ *
78
+ * @constant {number}
79
+ */
22
80
  export const WS_ID_UPTIME_UPDATE = 5;
81
+ /**
82
+ * Websocket message ID indicating a snackbar message.
83
+ *
84
+ * @constant {number}
85
+ */
23
86
  export const WS_ID_SNACKBAR = 6;
87
+ /**
88
+ * Websocket message ID indicating matterbridge has un update available.
89
+ *
90
+ * @constant {number}
91
+ */
24
92
  export const WS_ID_UPDATE_NEEDED = 7;
93
+ /**
94
+ * Websocket message ID indicating a state update.
95
+ *
96
+ * @constant {number}
97
+ */
25
98
  export const WS_ID_STATEUPDATE = 8;
99
+ /**
100
+ * Websocket message ID indicating to close a permanent snackbar message.
101
+ *
102
+ * @constant {number}
103
+ */
26
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
+ */
27
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
+ */
28
124
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
29
125
  export class Frontend extends EventEmitter {
30
126
  matterbridge;
@@ -38,7 +134,7 @@ export class Frontend extends EventEmitter {
38
134
  constructor(matterbridge) {
39
135
  super();
40
136
  this.matterbridge = matterbridge;
41
- 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 */ });
42
138
  }
43
139
  set logLevel(logLevel) {
44
140
  this.log.logLevel = logLevel;
@@ -46,12 +142,41 @@ export class Frontend extends EventEmitter {
46
142
  async start(port = 8283) {
47
143
  this.port = port;
48
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
49
146
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
50
147
  await fs.mkdir(uploadDir, { recursive: true });
51
148
  const upload = multer({ dest: uploadDir });
149
+ // Create the express app that serves the frontend
52
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
53
177
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
54
178
  if (!hasParameter('ssl')) {
179
+ // Create an HTTP server and attach the express app
55
180
  try {
56
181
  this.httpServer = createServer(this.expressApp);
57
182
  }
@@ -60,6 +185,7 @@ export class Frontend extends EventEmitter {
60
185
  this.emit('server_error', error);
61
186
  return;
62
187
  }
188
+ // Listen on the specified port
63
189
  if (hasParameter('ingress')) {
64
190
  this.httpServer.listen(this.port, '0.0.0.0', () => {
65
191
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -91,6 +217,7 @@ export class Frontend extends EventEmitter {
91
217
  });
92
218
  }
93
219
  else {
220
+ // Load the SSL certificate, the private key and optionally the CA certificate
94
221
  let cert;
95
222
  try {
96
223
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -120,6 +247,7 @@ export class Frontend extends EventEmitter {
120
247
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
121
248
  }
122
249
  const serverOptions = { cert, key, ca };
250
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
123
251
  try {
124
252
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
125
253
  }
@@ -128,6 +256,7 @@ export class Frontend extends EventEmitter {
128
256
  this.emit('server_error', error);
129
257
  return;
130
258
  }
259
+ // Listen on the specified port
131
260
  if (hasParameter('ingress')) {
132
261
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
133
262
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -160,16 +289,18 @@ export class Frontend extends EventEmitter {
160
289
  }
161
290
  if (this.initializeError)
162
291
  return;
292
+ // Create a WebSocket server and attach it to the http or https server
163
293
  const wssPort = this.port;
164
294
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
165
295
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
166
296
  this.webSocketServer.on('connection', (ws, request) => {
167
297
  const clientIp = request.socket.remoteAddress;
168
- let callbackLogLevel = "notice";
169
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
170
- callbackLogLevel = "info";
171
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
172
- 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 */;
173
304
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
174
305
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
175
306
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -204,6 +335,7 @@ export class Frontend extends EventEmitter {
204
335
  this.webSocketServer.on('error', (ws, error) => {
205
336
  this.log.error(`WebSocketServer error: ${error}`);
206
337
  });
338
+ // Subscribe to cli events
207
339
  cliEmitter.removeAllListeners();
208
340
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
209
341
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -214,6 +346,8 @@ export class Frontend extends EventEmitter {
214
346
  cliEmitter.on('cpu', (cpuUsage) => {
215
347
  this.wssSendCpuUpdate(cpuUsage);
216
348
  });
349
+ // Endpoint to validate login code
350
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
217
351
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
218
352
  const { password } = req.body;
219
353
  this.log.debug('The frontend sent /api/login', password);
@@ -232,23 +366,27 @@ export class Frontend extends EventEmitter {
232
366
  this.log.warn('/api/login error wrong password');
233
367
  res.json({ valid: false });
234
368
  }
369
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
235
370
  }
236
371
  catch (error) {
237
372
  this.log.error('/api/login error getting password');
238
373
  res.json({ valid: false });
239
374
  }
240
375
  });
376
+ // Endpoint to provide health check for docker
241
377
  this.expressApp.get('/health', (req, res) => {
242
378
  this.log.debug('Express received /health');
243
379
  const healthStatus = {
244
- status: 'ok',
245
- uptime: process.uptime(),
246
- 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
247
383
  };
248
384
  res.status(200).json(healthStatus);
249
385
  });
386
+ // Endpoint to provide memory usage details
250
387
  this.expressApp.get('/memory', async (req, res) => {
251
388
  this.log.debug('Express received /memory');
389
+ // Memory usage from process
252
390
  const memoryUsageRaw = process.memoryUsage();
253
391
  const memoryUsage = {
254
392
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -257,10 +395,13 @@ export class Frontend extends EventEmitter {
257
395
  external: this.formatMemoryUsage(memoryUsageRaw.external),
258
396
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
259
397
  };
398
+ // V8 heap statistics
260
399
  const { default: v8 } = await import('node:v8');
261
400
  const heapStatsRaw = v8.getHeapStatistics();
262
401
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
402
+ // Format heapStats
263
403
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
404
+ // Format heapSpaces
264
405
  const heapSpaces = heapSpacesRaw.map((space) => ({
265
406
  ...space,
266
407
  space_size: this.formatMemoryUsage(space.space_size),
@@ -278,19 +419,23 @@ export class Frontend extends EventEmitter {
278
419
  };
279
420
  res.status(200).json(memoryReport);
280
421
  });
422
+ // Endpoint to provide settings
281
423
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
282
424
  this.log.debug('The frontend sent /api/settings');
283
425
  res.json(await this.getApiSettings());
284
426
  });
427
+ // Endpoint to provide plugins
285
428
  this.expressApp.get('/api/plugins', async (req, res) => {
286
429
  this.log.debug('The frontend sent /api/plugins');
287
430
  res.json(this.getBaseRegisteredPlugins());
288
431
  });
432
+ // Endpoint to provide devices
289
433
  this.expressApp.get('/api/devices', async (req, res) => {
290
434
  this.log.debug('The frontend sent /api/devices');
291
435
  const devices = await this.getDevices();
292
436
  res.json(devices);
293
437
  });
438
+ // Endpoint to view the matterbridge log
294
439
  this.expressApp.get('/api/view-mblog', async (req, res) => {
295
440
  this.log.debug('The frontend sent /api/view-mblog');
296
441
  try {
@@ -303,6 +448,7 @@ export class Frontend extends EventEmitter {
303
448
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
304
449
  }
305
450
  });
451
+ // Endpoint to view the matter.js log
306
452
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
307
453
  this.log.debug('The frontend sent /api/view-mjlog');
308
454
  try {
@@ -315,6 +461,7 @@ export class Frontend extends EventEmitter {
315
461
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
316
462
  }
317
463
  });
464
+ // Endpoint to view the shelly log
318
465
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
319
466
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
320
467
  try {
@@ -327,9 +474,11 @@ export class Frontend extends EventEmitter {
327
474
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
328
475
  }
329
476
  });
477
+ // Endpoint to download the matterbridge log
330
478
  this.expressApp.get('/api/download-mblog', async (req, res) => {
331
479
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
332
480
  try {
481
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
333
482
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
334
483
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
335
484
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
@@ -340,15 +489,18 @@ export class Frontend extends EventEmitter {
340
489
  }
341
490
  res.type('text/plain');
342
491
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
492
+ /* istanbul ignore if */
343
493
  if (error) {
344
494
  this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
345
495
  res.status(500).send('Error downloading the matterbridge log file');
346
496
  }
347
497
  });
348
498
  });
499
+ // Endpoint to download the matter log
349
500
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
350
501
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
351
502
  try {
503
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
352
504
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
353
505
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
354
506
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
@@ -359,15 +511,18 @@ export class Frontend extends EventEmitter {
359
511
  }
360
512
  res.type('text/plain');
361
513
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
514
+ /* istanbul ignore if */
362
515
  if (error) {
363
516
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
364
517
  res.status(500).send('Error downloading the matter log file');
365
518
  }
366
519
  });
367
520
  });
521
+ // Endpoint to download the shelly log
368
522
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
369
523
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
370
524
  try {
525
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
371
526
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
372
527
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
373
528
  await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
@@ -378,74 +533,90 @@ export class Frontend extends EventEmitter {
378
533
  }
379
534
  res.type('text/plain');
380
535
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
536
+ /* istanbul ignore if */
381
537
  if (error) {
382
538
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
383
539
  res.status(500).send('Error downloading Shelly system log file');
384
540
  }
385
541
  });
386
542
  });
543
+ // Endpoint to download the matterbridge storage directory
387
544
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
388
545
  this.log.debug('The frontend sent /api/download-mbstorage');
389
546
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
390
547
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
548
+ /* istanbul ignore if */
391
549
  if (error) {
392
550
  this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
393
551
  res.status(500).send('Error downloading the matterbridge storage file');
394
552
  }
395
553
  });
396
554
  });
555
+ // Endpoint to download the matter storage file
397
556
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
398
557
  this.log.debug('The frontend sent /api/download-mjstorage');
399
558
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
400
559
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
560
+ /* istanbul ignore if */
401
561
  if (error) {
402
562
  this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
403
563
  res.status(500).send('Error downloading the matter storage zip file');
404
564
  }
405
565
  });
406
566
  });
567
+ // Endpoint to download the matterbridge plugin directory
407
568
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
408
569
  this.log.debug('The frontend sent /api/download-pluginstorage');
409
570
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
410
571
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
572
+ /* istanbul ignore if */
411
573
  if (error) {
412
574
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
413
575
  res.status(500).send('Error downloading the matterbridge plugin storage file');
414
576
  }
415
577
  });
416
578
  });
579
+ // Endpoint to download the matterbridge plugin config files
417
580
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
418
581
  this.log.debug('The frontend sent /api/download-pluginconfig');
419
582
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
420
583
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
584
+ /* istanbul ignore if */
421
585
  if (error) {
422
586
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
423
587
  res.status(500).send('Error downloading the matterbridge plugin config file');
424
588
  }
425
589
  });
426
590
  });
591
+ // Endpoint to download the matterbridge backup (created with the backup command)
427
592
  this.expressApp.get('/api/download-backup', async (req, res) => {
428
593
  this.log.debug('The frontend sent /api/download-backup');
429
594
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
595
+ /* istanbul ignore if */
430
596
  if (error) {
431
597
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
432
598
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
433
599
  }
434
600
  });
435
601
  });
602
+ // Endpoint to upload a package
436
603
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
437
604
  const { filename } = req.body;
438
605
  const file = req.file;
606
+ /* istanbul ignore if */
439
607
  if (!file || !filename) {
440
608
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
441
609
  res.status(400).send('Invalid request: file and filename are required');
442
610
  return;
443
611
  }
444
612
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
613
+ // Define the path where the plugin file will be saved
445
614
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
446
615
  try {
616
+ // Move the uploaded file to the specified path
447
617
  await fs.rename(file.path, filePath);
448
618
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
619
+ // Install the plugin package
449
620
  if (filename.endsWith('.tgz')) {
450
621
  const { spawnCommand } = await import('./utils/spawn.js');
451
622
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
@@ -465,6 +636,7 @@ export class Frontend extends EventEmitter {
465
636
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
466
637
  }
467
638
  });
639
+ // Fallback for routing (must be the last route)
468
640
  this.expressApp.use((req, res) => {
469
641
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
470
642
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -473,13 +645,16 @@ export class Frontend extends EventEmitter {
473
645
  }
474
646
  async stop() {
475
647
  this.log.debug('Stopping the frontend...');
648
+ // Remove listeners from the express app
476
649
  if (this.expressApp) {
477
650
  this.expressApp.removeAllListeners();
478
651
  this.expressApp = undefined;
479
652
  this.log.debug('Frontend app closed successfully');
480
653
  }
654
+ // Close the WebSocket server
481
655
  if (this.webSocketServer) {
482
656
  this.log.debug('Closing WebSocket server...');
657
+ // Close all active connections
483
658
  this.webSocketServer.clients.forEach((client) => {
484
659
  if (client.readyState === WebSocket.OPEN) {
485
660
  client.close();
@@ -499,6 +674,7 @@ export class Frontend extends EventEmitter {
499
674
  this.webSocketServer.removeAllListeners();
500
675
  this.webSocketServer = undefined;
501
676
  }
677
+ // Close the http server
502
678
  if (this.httpServer) {
503
679
  this.log.debug('Closing http server...');
504
680
  await withTimeout(new Promise((resolve) => {
@@ -516,6 +692,7 @@ export class Frontend extends EventEmitter {
516
692
  this.httpServer = undefined;
517
693
  this.log.debug('Frontend http server closed successfully');
518
694
  }
695
+ // Close the https server
519
696
  if (this.httpsServer) {
520
697
  this.log.debug('Closing https server...');
521
698
  await withTimeout(new Promise((resolve) => {
@@ -535,6 +712,7 @@ export class Frontend extends EventEmitter {
535
712
  }
536
713
  this.log.debug('Frontend stopped successfully');
537
714
  }
715
+ // Function to format bytes to KB, MB, or GB
538
716
  formatMemoryUsage = (bytes) => {
539
717
  if (bytes >= 1024 ** 3) {
540
718
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -546,6 +724,7 @@ export class Frontend extends EventEmitter {
546
724
  return `${(bytes / 1024).toFixed(2)} KB`;
547
725
  }
548
726
  };
727
+ // Function to format system uptime with only the most significant unit
549
728
  formatOsUpTime = (seconds) => {
550
729
  if (seconds >= 86400) {
551
730
  const days = Math.floor(seconds / 86400);
@@ -561,7 +740,13 @@ export class Frontend extends EventEmitter {
561
740
  }
562
741
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
563
742
  };
743
+ /**
744
+ * Retrieves the api settings data.
745
+ *
746
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
747
+ */
564
748
  async getApiSettings() {
749
+ // Update the system information
565
750
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
566
751
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
567
752
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -570,6 +755,7 @@ export class Frontend extends EventEmitter {
570
755
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
571
756
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
572
757
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
758
+ // Update the matterbridge information
573
759
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
574
760
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
575
761
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -588,6 +774,12 @@ export class Frontend extends EventEmitter {
588
774
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
589
775
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
590
776
  }
777
+ /**
778
+ * Retrieves the reachable attribute.
779
+ *
780
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
781
+ * @returns {boolean} The reachable attribute.
782
+ */
591
783
  getReachability(device) {
592
784
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
593
785
  return false;
@@ -599,6 +791,12 @@ export class Frontend extends EventEmitter {
599
791
  return true;
600
792
  return false;
601
793
  }
794
+ /**
795
+ * Retrieves the power source attribute.
796
+ *
797
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
798
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
799
+ */
602
800
  getPowerSource(endpoint) {
603
801
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
604
802
  return undefined;
@@ -614,13 +812,21 @@ export class Frontend extends EventEmitter {
614
812
  }
615
813
  return;
616
814
  };
815
+ // Root endpoint
617
816
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
618
817
  return powerSource(endpoint);
818
+ // Child endpoints
619
819
  for (const child of endpoint.getChildEndpoints()) {
620
820
  if (child.hasClusterServer(PowerSource.Cluster.id))
621
821
  return powerSource(child);
622
822
  }
623
823
  }
824
+ /**
825
+ * Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device.
826
+ *
827
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
828
+ * @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
829
+ */
624
830
  getMatterDataFromDevice(device) {
625
831
  if (device.mode === 'server' && device.serverNode) {
626
832
  return {
@@ -632,6 +838,13 @@ export class Frontend extends EventEmitter {
632
838
  };
633
839
  }
634
840
  }
841
+ /**
842
+ * Retrieves the cluster text description from a given device.
843
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
844
+ *
845
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
846
+ * @returns {string} The attributes description of the cluster servers in the device.
847
+ */
635
848
  getClusterTextFromDevice(device) {
636
849
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
637
850
  return '';
@@ -656,6 +869,7 @@ export class Frontend extends EventEmitter {
656
869
  let attributes = '';
657
870
  let supportedModes = [];
658
871
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
872
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
659
873
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
660
874
  return;
661
875
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -745,8 +959,14 @@ export class Frontend extends EventEmitter {
745
959
  if (clusterName === 'userLabel' && attributeName === 'labelList')
746
960
  attributes += `${getUserLabel(device)} `;
747
961
  });
962
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
748
963
  return attributes.trimStart().trimEnd();
749
964
  }
965
+ /**
966
+ * Retrieves the base registered plugins sanitized for res.json().
967
+ *
968
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
969
+ */
750
970
  getBaseRegisteredPlugins() {
751
971
  const baseRegisteredPlugins = [];
752
972
  for (const plugin of this.matterbridge.plugins) {
@@ -785,11 +1005,19 @@ export class Frontend extends EventEmitter {
785
1005
  }
786
1006
  return baseRegisteredPlugins;
787
1007
  }
1008
+ /**
1009
+ * Retrieves the devices from Matterbridge.
1010
+ *
1011
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1012
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1013
+ */
788
1014
  async getDevices(pluginName) {
789
1015
  const devices = [];
790
1016
  for (const device of this.matterbridge.devices.array()) {
1017
+ // Filter by pluginName if provided
791
1018
  if (pluginName && pluginName !== device.plugin)
792
1019
  continue;
1020
+ // Check if the device has the required properties
793
1021
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
794
1022
  continue;
795
1023
  devices.push({
@@ -809,22 +1037,37 @@ export class Frontend extends EventEmitter {
809
1037
  }
810
1038
  return devices;
811
1039
  }
1040
+ /**
1041
+ * Retrieves the clusters from a given plugin and endpoint number.
1042
+ *
1043
+ * Response for /api/clusters
1044
+ *
1045
+ * @param {string} pluginName - The name of the plugin.
1046
+ * @param {number} endpointNumber - The endpoint number.
1047
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1048
+ */
812
1049
  getClusters(pluginName, endpointNumber) {
813
1050
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
814
1051
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
815
1052
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
816
1053
  return;
817
1054
  }
1055
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1056
+ // Get the device types from the main endpoint
818
1057
  const deviceTypes = [];
819
1058
  const clusters = [];
820
1059
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
821
1060
  deviceTypes.push(d.deviceType);
822
1061
  });
1062
+ // Get the clusters from the main endpoint
823
1063
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
824
1064
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
825
1065
  return;
826
1066
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
827
1067
  return;
1068
+ // console.log(
1069
+ // `${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}`,
1070
+ // );
828
1071
  clusters.push({
829
1072
  endpoint: endpoint.number.toString(),
830
1073
  id: 'main',
@@ -837,12 +1080,18 @@ export class Frontend extends EventEmitter {
837
1080
  attributeLocalValue: attributeValue,
838
1081
  });
839
1082
  });
1083
+ // Get the child endpoints
840
1084
  const childEndpoints = endpoint.getChildEndpoints();
1085
+ // if (childEndpoints.length === 0) {
1086
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1087
+ // }
841
1088
  childEndpoints.forEach((childEndpoint) => {
842
1089
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
843
1090
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
844
1091
  return;
845
1092
  }
1093
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1094
+ // Get the device types of the child endpoint
846
1095
  const deviceTypes = [];
847
1096
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
848
1097
  deviceTypes.push(d.deviceType);
@@ -852,9 +1101,12 @@ export class Frontend extends EventEmitter {
852
1101
  return;
853
1102
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
854
1103
  return;
1104
+ // console.log(
1105
+ // `${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}`,
1106
+ // );
855
1107
  clusters.push({
856
1108
  endpoint: childEndpoint.number.toString(),
857
- id: childEndpoint.maybeId ?? 'null',
1109
+ id: childEndpoint.maybeId ?? 'null', // Never happens
858
1110
  deviceTypes,
859
1111
  clusterName: capitalizeFirstLetter(clusterName),
860
1112
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -867,6 +1119,13 @@ export class Frontend extends EventEmitter {
867
1119
  });
868
1120
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
869
1121
  }
1122
+ /**
1123
+ * Handles incoming websocket messages for the Matterbridge frontend.
1124
+ *
1125
+ * @param {WebSocket} client - The websocket client that sent the message.
1126
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1127
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1128
+ */
870
1129
  async wsMessageHandler(client, message) {
871
1130
  let data;
872
1131
  try {
@@ -913,32 +1172,41 @@ export class Frontend extends EventEmitter {
913
1172
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
914
1173
  const packageName = data.params.packageName.replace(/@.*$/, '');
915
1174
  if (data.params.restart === false && packageName !== 'matterbridge') {
1175
+ // The install comes from InstallPlugins
916
1176
  this.matterbridge.plugins
917
1177
  .add(packageName)
918
1178
  .then((plugin) => {
919
1179
  if (plugin) {
1180
+ // The plugin is not registered
920
1181
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
921
1182
  this.matterbridge.plugins
922
1183
  .load(plugin, true, 'The plugin has been added', true)
1184
+ // eslint-disable-next-line promise/no-nesting
923
1185
  .then(() => {
924
1186
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
925
1187
  this.wssSendRefreshRequired('plugins');
926
1188
  return;
927
1189
  })
1190
+ // eslint-disable-next-line promise/no-nesting
928
1191
  .catch((_error) => {
1192
+ //
929
1193
  });
930
1194
  }
931
1195
  else {
1196
+ // The plugin is already registered
932
1197
  this.wssSendSnackbarMessage(`Restart required`, 0);
933
1198
  this.wssSendRefreshRequired('plugins');
934
1199
  this.wssSendRestartRequired();
935
1200
  }
936
1201
  return;
937
1202
  })
1203
+ // eslint-disable-next-line promise/no-nesting
938
1204
  .catch((_error) => {
1205
+ //
939
1206
  });
940
1207
  }
941
1208
  else {
1209
+ // The package is matterbridge
942
1210
  if (this.matterbridge.restartMode !== '') {
943
1211
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
944
1212
  this.matterbridge.shutdownProcess();
@@ -961,6 +1229,7 @@ export class Frontend extends EventEmitter {
961
1229
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
962
1230
  return;
963
1231
  }
1232
+ // The package is a plugin
964
1233
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
965
1234
  if (plugin) {
966
1235
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -969,6 +1238,7 @@ export class Frontend extends EventEmitter {
969
1238
  this.wssSendRefreshRequired('plugins');
970
1239
  this.wssSendRefreshRequired('devices');
971
1240
  }
1241
+ // Uninstall the package
972
1242
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
973
1243
  const { spawnCommand } = await import('./utils/spawn.js');
974
1244
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1009,6 +1279,7 @@ export class Frontend extends EventEmitter {
1009
1279
  return;
1010
1280
  })
1011
1281
  .catch((_error) => {
1282
+ //
1012
1283
  });
1013
1284
  }
1014
1285
  else {
@@ -1055,6 +1326,7 @@ export class Frontend extends EventEmitter {
1055
1326
  return;
1056
1327
  })
1057
1328
  .catch((_error) => {
1329
+ //
1058
1330
  });
1059
1331
  }
1060
1332
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1289,22 +1561,22 @@ export class Frontend extends EventEmitter {
1289
1561
  if (isValidString(data.params.value, 4)) {
1290
1562
  this.log.debug('Matterbridge logger level:', data.params.value);
1291
1563
  if (data.params.value === 'Debug') {
1292
- await this.matterbridge.setLogLevel("debug");
1564
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1293
1565
  }
1294
1566
  else if (data.params.value === 'Info') {
1295
- await this.matterbridge.setLogLevel("info");
1567
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1296
1568
  }
1297
1569
  else if (data.params.value === 'Notice') {
1298
- await this.matterbridge.setLogLevel("notice");
1570
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1299
1571
  }
1300
1572
  else if (data.params.value === 'Warn') {
1301
- await this.matterbridge.setLogLevel("warn");
1573
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1302
1574
  }
1303
1575
  else if (data.params.value === 'Error') {
1304
- await this.matterbridge.setLogLevel("error");
1576
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1305
1577
  }
1306
1578
  else if (data.params.value === 'Fatal') {
1307
- await this.matterbridge.setLogLevel("fatal");
1579
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1308
1580
  }
1309
1581
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1310
1582
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1315,6 +1587,7 @@ export class Frontend extends EventEmitter {
1315
1587
  this.log.debug('Matterbridge file log:', data.params.value);
1316
1588
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1317
1589
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1590
+ // Create the file logger for matterbridge
1318
1591
  if (data.params.value)
1319
1592
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1320
1593
  else
@@ -1361,6 +1634,7 @@ export class Frontend extends EventEmitter {
1361
1634
  });
1362
1635
  }
1363
1636
  catch (error) {
1637
+ /* istanbul ignore next */
1364
1638
  this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1365
1639
  }
1366
1640
  }
@@ -1369,6 +1643,7 @@ export class Frontend extends EventEmitter {
1369
1643
  Logger.removeLogger('matterfilelogger');
1370
1644
  }
1371
1645
  catch (error) {
1646
+ /* istanbul ignore next */
1372
1647
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1373
1648
  }
1374
1649
  }
@@ -1481,15 +1756,19 @@ export class Frontend extends EventEmitter {
1481
1756
  return;
1482
1757
  }
1483
1758
  const config = plugin.configJson;
1759
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1484
1760
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1761
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1485
1762
  if (select === 'serial')
1486
1763
  this.log.info(`Selected device serial ${data.params.serial}`);
1487
1764
  if (select === 'name')
1488
1765
  this.log.info(`Selected device name ${data.params.name}`);
1489
1766
  if (config && select && (select === 'serial' || select === 'name')) {
1767
+ // Remove postfix from the serial if it exists
1490
1768
  if (config.postfix) {
1491
1769
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1492
1770
  }
1771
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1493
1772
  if (isValidArray(config.whiteList, 1)) {
1494
1773
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1495
1774
  config.whiteList.push(data.params.serial);
@@ -1498,6 +1777,7 @@ export class Frontend extends EventEmitter {
1498
1777
  config.whiteList.push(data.params.name);
1499
1778
  }
1500
1779
  }
1780
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1501
1781
  if (isValidArray(config.blackList, 1)) {
1502
1782
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1503
1783
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1528,7 +1808,9 @@ export class Frontend extends EventEmitter {
1528
1808
  return;
1529
1809
  }
1530
1810
  const config = plugin.configJson;
1811
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1531
1812
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1813
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1532
1814
  if (select === 'serial')
1533
1815
  this.log.info(`Unselected device serial ${data.params.serial}`);
1534
1816
  if (select === 'name')
@@ -1537,6 +1819,7 @@ export class Frontend extends EventEmitter {
1537
1819
  if (config.postfix) {
1538
1820
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1539
1821
  }
1822
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1540
1823
  if (isValidArray(config.whiteList, 1)) {
1541
1824
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1542
1825
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1545,6 +1828,7 @@ export class Frontend extends EventEmitter {
1545
1828
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1546
1829
  }
1547
1830
  }
1831
+ // Add the serial to the blackList
1548
1832
  if (isValidArray(config.blackList)) {
1549
1833
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1550
1834
  config.blackList.push(data.params.serial);
@@ -1578,114 +1862,230 @@ export class Frontend extends EventEmitter {
1578
1862
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1579
1863
  }
1580
1864
  }
1865
+ /**
1866
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1867
+ *
1868
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1869
+ * @param {string} time - The time string of the message
1870
+ * @param {string} name - The logger name of the message
1871
+ * @param {string} message - The content of the message.
1872
+ *
1873
+ * @remarks
1874
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1875
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1876
+ * The function sends the message to all connected clients.
1877
+ */
1581
1878
  wssSendMessage(level, time, name, message) {
1582
1879
  if (!level || !time || !name || !message)
1583
1880
  return;
1881
+ // Remove ANSI escape codes from the message
1882
+ // eslint-disable-next-line no-control-regex
1584
1883
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1884
+ // Remove leading asterisks from the message
1585
1885
  message = message.replace(/^\*+/, '');
1886
+ // Replace all occurrences of \t and \n
1586
1887
  message = message.replace(/[\t\n]/g, '');
1888
+ // Remove non-printable characters
1889
+ // eslint-disable-next-line no-control-regex
1587
1890
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1891
+ // Replace all occurrences of \" with "
1588
1892
  message = message.replace(/\\"/g, '"');
1893
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1589
1894
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1895
+ // Define the maximum allowed length for continuous characters without a space
1590
1896
  const maxContinuousLength = 100;
1591
1897
  const keepStartLength = 20;
1592
1898
  const keepEndLength = 20;
1899
+ // Split the message into words
1593
1900
  message = message
1594
1901
  .split(' ')
1595
1902
  .map((word) => {
1903
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1596
1904
  if (word.length > maxContinuousLength) {
1597
1905
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1598
1906
  }
1599
1907
  return word;
1600
1908
  })
1601
1909
  .join(' ');
1910
+ // Send the message to all connected clients
1602
1911
  this.webSocketServer?.clients.forEach((client) => {
1603
1912
  if (client.readyState === WebSocket.OPEN) {
1604
1913
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1605
1914
  }
1606
1915
  });
1607
1916
  }
1917
+ /**
1918
+ * Sends a need to refresh WebSocket message to all connected clients.
1919
+ *
1920
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1921
+ * possible values:
1922
+ * - 'matterbridgeLatestVersion'
1923
+ * - 'matterbridgeAdvertise'
1924
+ * - 'online'
1925
+ * - 'offline'
1926
+ * - 'reachability'
1927
+ * - 'settings'
1928
+ * - 'plugins'
1929
+ * - 'pluginsRestart'
1930
+ * - 'devices'
1931
+ * - 'fabrics'
1932
+ * - 'sessions'
1933
+ */
1608
1934
  wssSendRefreshRequired(changed = null) {
1609
1935
  this.log.debug('Sending a refresh required message to all connected clients');
1936
+ // Send the message to all connected clients
1610
1937
  this.webSocketServer?.clients.forEach((client) => {
1611
1938
  if (client.readyState === WebSocket.OPEN) {
1612
1939
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1613
1940
  }
1614
1941
  });
1615
1942
  }
1943
+ /**
1944
+ * Sends a need to restart WebSocket message to all connected clients.
1945
+ *
1946
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
1947
+ */
1616
1948
  wssSendRestartRequired(snackbar = true) {
1617
1949
  this.log.debug('Sending a restart required message to all connected clients');
1618
1950
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1619
1951
  if (snackbar === true)
1620
1952
  this.wssSendSnackbarMessage(`Restart required`, 0);
1953
+ // Send the message to all connected clients
1621
1954
  this.webSocketServer?.clients.forEach((client) => {
1622
1955
  if (client.readyState === WebSocket.OPEN) {
1623
1956
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1624
1957
  }
1625
1958
  });
1626
1959
  }
1960
+ /**
1961
+ * Sends a need to update WebSocket message to all connected clients.
1962
+ *
1963
+ */
1627
1964
  wssSendUpdateRequired() {
1628
1965
  this.log.debug('Sending an update required message to all connected clients');
1629
1966
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1967
+ // Send the message to all connected clients
1630
1968
  this.webSocketServer?.clients.forEach((client) => {
1631
1969
  if (client.readyState === WebSocket.OPEN) {
1632
1970
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1633
1971
  }
1634
1972
  });
1635
1973
  }
1974
+ /**
1975
+ * Sends a cpu update message to all connected clients.
1976
+ *
1977
+ * @param {number} cpuUsage - The CPU usage percentage to send.
1978
+ */
1636
1979
  wssSendCpuUpdate(cpuUsage) {
1637
1980
  if (hasParameter('debug'))
1638
1981
  this.log.debug('Sending a cpu update message to all connected clients');
1982
+ // Send the message to all connected clients
1639
1983
  this.webSocketServer?.clients.forEach((client) => {
1640
1984
  if (client.readyState === WebSocket.OPEN) {
1641
1985
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1642
1986
  }
1643
1987
  });
1644
1988
  }
1989
+ /**
1990
+ * Sends a memory update message to all connected clients.
1991
+ *
1992
+ * @param {string} totalMemory - The total memory in bytes.
1993
+ * @param {string} freeMemory - The free memory in bytes.
1994
+ * @param {string} rss - The resident set size in bytes.
1995
+ * @param {string} heapTotal - The total heap memory in bytes.
1996
+ * @param {string} heapUsed - The used heap memory in bytes.
1997
+ * @param {string} external - The external memory in bytes.
1998
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
1999
+ */
1645
2000
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1646
2001
  if (hasParameter('debug'))
1647
2002
  this.log.debug('Sending a memory update message to all connected clients');
2003
+ // Send the message to all connected clients
1648
2004
  this.webSocketServer?.clients.forEach((client) => {
1649
2005
  if (client.readyState === WebSocket.OPEN) {
1650
2006
  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
2007
  }
1652
2008
  });
1653
2009
  }
2010
+ /**
2011
+ * Sends an uptime update message to all connected clients.
2012
+ *
2013
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2014
+ * @param {string} processUptime - The process uptime in a human-readable format.
2015
+ */
1654
2016
  wssSendUptimeUpdate(systemUptime, processUptime) {
1655
2017
  if (hasParameter('debug'))
1656
2018
  this.log.debug('Sending a uptime update message to all connected clients');
2019
+ // Send the message to all connected clients
1657
2020
  this.webSocketServer?.clients.forEach((client) => {
1658
2021
  if (client.readyState === WebSocket.OPEN) {
1659
2022
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1660
2023
  }
1661
2024
  });
1662
2025
  }
2026
+ /**
2027
+ * Sends an open snackbar message to all connected clients.
2028
+ *
2029
+ * @param {string} message - The message to send.
2030
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
2031
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2032
+ */
1663
2033
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1664
2034
  this.log.debug('Sending a snackbar message to all connected clients');
2035
+ // Send the message to all connected clients
1665
2036
  this.webSocketServer?.clients.forEach((client) => {
1666
2037
  if (client.readyState === WebSocket.OPEN) {
1667
2038
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1668
2039
  }
1669
2040
  });
1670
2041
  }
2042
+ /**
2043
+ * Sends a close snackbar message to all connected clients.
2044
+ *
2045
+ * @param {string} message - The message to send.
2046
+ */
1671
2047
  wssSendCloseSnackbarMessage(message) {
1672
2048
  this.log.debug('Sending a close snackbar message to all connected clients');
2049
+ // Send the message to all connected clients
1673
2050
  this.webSocketServer?.clients.forEach((client) => {
1674
2051
  if (client.readyState === WebSocket.OPEN) {
1675
2052
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1676
2053
  }
1677
2054
  });
1678
2055
  }
2056
+ /**
2057
+ * Sends an attribute update message to all connected WebSocket clients.
2058
+ *
2059
+ * @param {string | undefined} plugin - The name of the plugin.
2060
+ * @param {string | undefined} serialNumber - The serial number of the device.
2061
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2062
+ * @param {string} cluster - The cluster name where the attribute belongs.
2063
+ * @param {string} attribute - The name of the attribute that changed.
2064
+ * @param {number | string | boolean} value - The new value of the attribute.
2065
+ *
2066
+ * @remarks
2067
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2068
+ * with the updated attribute information.
2069
+ */
1679
2070
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1680
2071
  this.log.debug('Sending an attribute update message to all connected clients');
2072
+ // Send the message to all connected clients
1681
2073
  this.webSocketServer?.clients.forEach((client) => {
1682
2074
  if (client.readyState === WebSocket.OPEN) {
1683
2075
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1684
2076
  }
1685
2077
  });
1686
2078
  }
2079
+ /**
2080
+ * Sends a message to all connected clients.
2081
+ *
2082
+ * @param {number} id - The message id.
2083
+ * @param {string} method - The message method.
2084
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
2085
+ */
1687
2086
  wssBroadcastMessage(id, method, params) {
1688
2087
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2088
+ // Send the message to all connected clients
1689
2089
  this.webSocketServer?.clients.forEach((client) => {
1690
2090
  if (client.readyState === WebSocket.OPEN) {
1691
2091
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1693,3 +2093,4 @@ export class Frontend extends EventEmitter {
1693
2093
  });
1694
2094
  }
1695
2095
  }
2096
+ //# sourceMappingURL=frontend.js.map