matterbridge 3.1.3-dev-20250714-03e05f2 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/cli.d.ts +26 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +91 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cliEmitter.d.ts +34 -0
  6. package/dist/cliEmitter.d.ts.map +1 -0
  7. package/dist/cliEmitter.js +30 -0
  8. package/dist/cliEmitter.js.map +1 -0
  9. package/dist/clusters/export.d.ts +2 -0
  10. package/dist/clusters/export.d.ts.map +1 -0
  11. package/dist/clusters/export.js +2 -0
  12. package/dist/clusters/export.js.map +1 -0
  13. package/dist/defaultConfigSchema.d.ts +28 -0
  14. package/dist/defaultConfigSchema.d.ts.map +1 -0
  15. package/dist/defaultConfigSchema.js +24 -0
  16. package/dist/defaultConfigSchema.js.map +1 -0
  17. package/dist/deviceManager.d.ts +112 -0
  18. package/dist/deviceManager.d.ts.map +1 -0
  19. package/dist/deviceManager.js +94 -1
  20. package/dist/deviceManager.js.map +1 -0
  21. package/dist/devices/batteryStorage.d.ts +48 -0
  22. package/dist/devices/batteryStorage.d.ts.map +1 -0
  23. package/dist/devices/batteryStorage.js +48 -1
  24. package/dist/devices/batteryStorage.js.map +1 -0
  25. package/dist/devices/evse.d.ts +75 -0
  26. package/dist/devices/evse.d.ts.map +1 -0
  27. package/dist/devices/evse.js +74 -10
  28. package/dist/devices/evse.js.map +1 -0
  29. package/dist/devices/export.d.ts +9 -0
  30. package/dist/devices/export.d.ts.map +1 -0
  31. package/dist/devices/export.js +2 -0
  32. package/dist/devices/export.js.map +1 -0
  33. package/dist/devices/heatPump.d.ts +47 -0
  34. package/dist/devices/heatPump.d.ts.map +1 -0
  35. package/dist/devices/heatPump.js +50 -2
  36. package/dist/devices/heatPump.js.map +1 -0
  37. package/dist/devices/laundryDryer.d.ts +87 -0
  38. package/dist/devices/laundryDryer.d.ts.map +1 -0
  39. package/dist/devices/laundryDryer.js +83 -6
  40. package/dist/devices/laundryDryer.js.map +1 -0
  41. package/dist/devices/laundryWasher.d.ts +242 -0
  42. package/dist/devices/laundryWasher.d.ts.map +1 -0
  43. package/dist/devices/laundryWasher.js +91 -7
  44. package/dist/devices/laundryWasher.js.map +1 -0
  45. package/dist/devices/roboticVacuumCleaner.d.ts +110 -0
  46. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  47. package/dist/devices/roboticVacuumCleaner.js +89 -6
  48. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  49. package/dist/devices/solarPower.d.ts +40 -0
  50. package/dist/devices/solarPower.d.ts.map +1 -0
  51. package/dist/devices/solarPower.js +38 -0
  52. package/dist/devices/solarPower.js.map +1 -0
  53. package/dist/devices/waterHeater.d.ts +111 -0
  54. package/dist/devices/waterHeater.d.ts.map +1 -0
  55. package/dist/devices/waterHeater.js +82 -2
  56. package/dist/devices/waterHeater.js.map +1 -0
  57. package/dist/frontend.d.ts +303 -0
  58. package/dist/frontend.d.ts.map +1 -0
  59. package/dist/frontend.js +423 -18
  60. package/dist/frontend.js.map +1 -0
  61. package/dist/globalMatterbridge.d.ts +59 -0
  62. package/dist/globalMatterbridge.d.ts.map +1 -0
  63. package/dist/globalMatterbridge.js +47 -0
  64. package/dist/globalMatterbridge.js.map +1 -0
  65. package/dist/helpers.d.ts +48 -0
  66. package/dist/helpers.d.ts.map +1 -0
  67. package/dist/helpers.js +53 -0
  68. package/dist/helpers.js.map +1 -0
  69. package/dist/index.d.ts +33 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +30 -1
  72. package/dist/index.js.map +1 -0
  73. package/dist/logger/export.d.ts +2 -0
  74. package/dist/logger/export.d.ts.map +1 -0
  75. package/dist/logger/export.js +1 -0
  76. package/dist/logger/export.js.map +1 -0
  77. package/dist/matter/behaviors.d.ts +2 -0
  78. package/dist/matter/behaviors.d.ts.map +1 -0
  79. package/dist/matter/behaviors.js +2 -0
  80. package/dist/matter/behaviors.js.map +1 -0
  81. package/dist/matter/clusters.d.ts +2 -0
  82. package/dist/matter/clusters.d.ts.map +1 -0
  83. package/dist/matter/clusters.js +2 -0
  84. package/dist/matter/clusters.js.map +1 -0
  85. package/dist/matter/devices.d.ts +2 -0
  86. package/dist/matter/devices.d.ts.map +1 -0
  87. package/dist/matter/devices.js +2 -0
  88. package/dist/matter/devices.js.map +1 -0
  89. package/dist/matter/endpoints.d.ts +2 -0
  90. package/dist/matter/endpoints.d.ts.map +1 -0
  91. package/dist/matter/endpoints.js +2 -0
  92. package/dist/matter/endpoints.js.map +1 -0
  93. package/dist/matter/export.d.ts +5 -0
  94. package/dist/matter/export.d.ts.map +1 -0
  95. package/dist/matter/export.js +3 -0
  96. package/dist/matter/export.js.map +1 -0
  97. package/dist/matter/types.d.ts +3 -0
  98. package/dist/matter/types.d.ts.map +1 -0
  99. package/dist/matter/types.js +3 -0
  100. package/dist/matter/types.js.map +1 -0
  101. package/dist/matterbridge.d.ts +444 -0
  102. package/dist/matterbridge.d.ts.map +1 -0
  103. package/dist/matterbridge.js +784 -51
  104. package/dist/matterbridge.js.map +1 -0
  105. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  106. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  107. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  108. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  109. package/dist/matterbridgeBehaviors.d.ts +1340 -0
  110. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  111. package/dist/matterbridgeBehaviors.js +61 -1
  112. package/dist/matterbridgeBehaviors.js.map +1 -0
  113. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  114. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  115. package/dist/matterbridgeDeviceTypes.js +579 -15
  116. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  117. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  118. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  119. package/dist/matterbridgeDynamicPlatform.js +36 -0
  120. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  121. package/dist/matterbridgeEndpoint.d.ts +1250 -0
  122. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  123. package/dist/matterbridgeEndpoint.js +1106 -42
  124. package/dist/matterbridgeEndpoint.js.map +1 -0
  125. package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
  126. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  127. package/dist/matterbridgeEndpointHelpers.js +322 -12
  128. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  129. package/dist/matterbridgePlatform.d.ts +310 -0
  130. package/dist/matterbridgePlatform.d.ts.map +1 -0
  131. package/dist/matterbridgePlatform.js +233 -0
  132. package/dist/matterbridgePlatform.js.map +1 -0
  133. package/dist/matterbridgeTypes.d.ts +195 -0
  134. package/dist/matterbridgeTypes.d.ts.map +1 -0
  135. package/dist/matterbridgeTypes.js +25 -0
  136. package/dist/matterbridgeTypes.js.map +1 -0
  137. package/dist/pluginManager.d.ts +291 -0
  138. package/dist/pluginManager.d.ts.map +1 -0
  139. package/dist/pluginManager.js +269 -3
  140. package/dist/pluginManager.js.map +1 -0
  141. package/dist/shelly.d.ts +174 -0
  142. package/dist/shelly.d.ts.map +1 -0
  143. package/dist/shelly.js +168 -7
  144. package/dist/shelly.js.map +1 -0
  145. package/dist/storage/export.d.ts +2 -0
  146. package/dist/storage/export.d.ts.map +1 -0
  147. package/dist/storage/export.js +1 -0
  148. package/dist/storage/export.js.map +1 -0
  149. package/dist/update.d.ts +59 -0
  150. package/dist/update.d.ts.map +1 -0
  151. package/dist/update.js +54 -0
  152. package/dist/update.js.map +1 -0
  153. package/dist/utils/colorUtils.d.ts +117 -0
  154. package/dist/utils/colorUtils.d.ts.map +1 -0
  155. package/dist/utils/colorUtils.js +263 -2
  156. package/dist/utils/colorUtils.js.map +1 -0
  157. package/dist/utils/commandLine.d.ts +59 -0
  158. package/dist/utils/commandLine.d.ts.map +1 -0
  159. package/dist/utils/commandLine.js +54 -0
  160. package/dist/utils/commandLine.js.map +1 -0
  161. package/dist/utils/copyDirectory.d.ts +33 -0
  162. package/dist/utils/copyDirectory.d.ts.map +1 -0
  163. package/dist/utils/copyDirectory.js +38 -1
  164. package/dist/utils/copyDirectory.js.map +1 -0
  165. package/dist/utils/createDirectory.d.ts +34 -0
  166. package/dist/utils/createDirectory.d.ts.map +1 -0
  167. package/dist/utils/createDirectory.js +33 -0
  168. package/dist/utils/createDirectory.js.map +1 -0
  169. package/dist/utils/createZip.d.ts +39 -0
  170. package/dist/utils/createZip.d.ts.map +1 -0
  171. package/dist/utils/createZip.js +47 -2
  172. package/dist/utils/createZip.js.map +1 -0
  173. package/dist/utils/deepCopy.d.ts +32 -0
  174. package/dist/utils/deepCopy.d.ts.map +1 -0
  175. package/dist/utils/deepCopy.js +39 -0
  176. package/dist/utils/deepCopy.js.map +1 -0
  177. package/dist/utils/deepEqual.d.ts +54 -0
  178. package/dist/utils/deepEqual.d.ts.map +1 -0
  179. package/dist/utils/deepEqual.js +72 -1
  180. package/dist/utils/deepEqual.js.map +1 -0
  181. package/dist/utils/export.d.ts +12 -0
  182. package/dist/utils/export.d.ts.map +1 -0
  183. package/dist/utils/export.js +1 -0
  184. package/dist/utils/export.js.map +1 -0
  185. package/dist/utils/hex.d.ts +49 -0
  186. package/dist/utils/hex.d.ts.map +1 -0
  187. package/dist/utils/hex.js +58 -0
  188. package/dist/utils/hex.js.map +1 -0
  189. package/dist/utils/isvalid.d.ts +103 -0
  190. package/dist/utils/isvalid.d.ts.map +1 -0
  191. package/dist/utils/isvalid.js +101 -0
  192. package/dist/utils/isvalid.js.map +1 -0
  193. package/dist/utils/network.d.ts +76 -0
  194. package/dist/utils/network.d.ts.map +1 -0
  195. package/dist/utils/network.js +83 -5
  196. package/dist/utils/network.js.map +1 -0
  197. package/dist/utils/spawn.d.ts +11 -0
  198. package/dist/utils/spawn.d.ts.map +1 -0
  199. package/dist/utils/spawn.js +18 -0
  200. package/dist/utils/spawn.js.map +1 -0
  201. package/dist/utils/wait.d.ts +56 -0
  202. package/dist/utils/wait.d.ts.map +1 -0
  203. package/dist/utils/wait.js +62 -9
  204. package/dist/utils/wait.js.map +1 -0
  205. package/npm-shrinkwrap.json +2 -2
  206. 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.profile = this.matterbridge.profile;
@@ -581,6 +767,7 @@ export class Frontend extends EventEmitter {
581
767
  this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
582
768
  this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
583
769
  this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
770
+ // Update the matterbridge information in bridge mode
584
771
  if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode) {
585
772
  this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
586
773
  this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
@@ -590,6 +777,12 @@ export class Frontend extends EventEmitter {
590
777
  }
591
778
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
592
779
  }
780
+ /**
781
+ * Retrieves the reachable attribute.
782
+ *
783
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
784
+ * @returns {boolean} The reachable attribute.
785
+ */
593
786
  getReachability(device) {
594
787
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
595
788
  return false;
@@ -601,6 +794,12 @@ export class Frontend extends EventEmitter {
601
794
  return true;
602
795
  return false;
603
796
  }
797
+ /**
798
+ * Retrieves the power source attribute.
799
+ *
800
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
801
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
802
+ */
604
803
  getPowerSource(endpoint) {
605
804
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
606
805
  return undefined;
@@ -616,13 +815,21 @@ export class Frontend extends EventEmitter {
616
815
  }
617
816
  return;
618
817
  };
818
+ // Root endpoint
619
819
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
620
820
  return powerSource(endpoint);
821
+ // Child endpoints
621
822
  for (const child of endpoint.getChildEndpoints()) {
622
823
  if (child.hasClusterServer(PowerSource.Cluster.id))
623
824
  return powerSource(child);
624
825
  }
625
826
  }
827
+ /**
828
+ * Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
829
+ *
830
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
831
+ * @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
832
+ */
626
833
  getMatterDataFromDevice(device) {
627
834
  if (device.mode === 'server' && device.serverNode) {
628
835
  return {
@@ -634,6 +841,13 @@ export class Frontend extends EventEmitter {
634
841
  };
635
842
  }
636
843
  }
844
+ /**
845
+ * Retrieves the cluster text description from a given device.
846
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
847
+ *
848
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
849
+ * @returns {string} The attributes description of the cluster servers in the device.
850
+ */
637
851
  getClusterTextFromDevice(device) {
638
852
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
639
853
  return '';
@@ -658,6 +872,7 @@ export class Frontend extends EventEmitter {
658
872
  let attributes = '';
659
873
  let supportedModes = [];
660
874
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
875
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
661
876
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
662
877
  return;
663
878
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -747,11 +962,17 @@ export class Frontend extends EventEmitter {
747
962
  if (clusterName === 'userLabel' && attributeName === 'labelList')
748
963
  attributes += `${getUserLabel(device)} `;
749
964
  });
965
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
750
966
  return attributes.trimStart().trimEnd();
751
967
  }
968
+ /**
969
+ * Retrieves the base registered plugins sanitized for res.json().
970
+ *
971
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
972
+ */
752
973
  getBaseRegisteredPlugins() {
753
974
  if (this.matterbridge.hasCleanupStarted)
754
- return [];
975
+ return []; // Skip if cleanup has started
755
976
  const baseRegisteredPlugins = [];
756
977
  for (const plugin of this.matterbridge.plugins) {
757
978
  baseRegisteredPlugins.push({
@@ -780,6 +1001,7 @@ export class Frontend extends EventEmitter {
780
1001
  schemaJson: plugin.schemaJson,
781
1002
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
782
1003
  hasBlackList: plugin.configJson?.blackList !== undefined,
1004
+ // Childbridge mode specific data
783
1005
  paired: plugin.serverNode?.state.commissioning.commissioned,
784
1006
  qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.qrPairingCode,
785
1007
  manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.manualPairingCode,
@@ -789,13 +1011,21 @@ export class Frontend extends EventEmitter {
789
1011
  }
790
1012
  return baseRegisteredPlugins;
791
1013
  }
1014
+ /**
1015
+ * Retrieves the devices from Matterbridge.
1016
+ *
1017
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1018
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1019
+ */
792
1020
  async getDevices(pluginName) {
793
1021
  if (this.matterbridge.hasCleanupStarted)
794
- return [];
1022
+ return []; // Skip if cleanup has started
795
1023
  const devices = [];
796
1024
  for (const device of this.matterbridge.devices.array()) {
1025
+ // Filter by pluginName if provided
797
1026
  if (pluginName && pluginName !== device.plugin)
798
1027
  continue;
1028
+ // Check if the device has the required properties
799
1029
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
800
1030
  continue;
801
1031
  devices.push({
@@ -815,22 +1045,37 @@ export class Frontend extends EventEmitter {
815
1045
  }
816
1046
  return devices;
817
1047
  }
1048
+ /**
1049
+ * Retrieves the clusters from a given plugin and endpoint number.
1050
+ *
1051
+ * Response for /api/clusters
1052
+ *
1053
+ * @param {string} pluginName - The name of the plugin.
1054
+ * @param {number} endpointNumber - The endpoint number.
1055
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1056
+ */
818
1057
  getClusters(pluginName, endpointNumber) {
819
1058
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
820
1059
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
821
1060
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
822
1061
  return;
823
1062
  }
1063
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1064
+ // Get the device types from the main endpoint
824
1065
  const deviceTypes = [];
825
1066
  const clusters = [];
826
1067
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
827
1068
  deviceTypes.push(d.deviceType);
828
1069
  });
1070
+ // Get the clusters from the main endpoint
829
1071
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
830
1072
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
831
1073
  return;
832
1074
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
833
1075
  return;
1076
+ // console.log(
1077
+ // `${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}`,
1078
+ // );
834
1079
  clusters.push({
835
1080
  endpoint: endpoint.number.toString(),
836
1081
  id: 'main',
@@ -843,12 +1088,18 @@ export class Frontend extends EventEmitter {
843
1088
  attributeLocalValue: attributeValue,
844
1089
  });
845
1090
  });
1091
+ // Get the child endpoints
846
1092
  const childEndpoints = endpoint.getChildEndpoints();
1093
+ // if (childEndpoints.length === 0) {
1094
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1095
+ // }
847
1096
  childEndpoints.forEach((childEndpoint) => {
848
1097
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
849
1098
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
850
1099
  return;
851
1100
  }
1101
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1102
+ // Get the device types of the child endpoint
852
1103
  const deviceTypes = [];
853
1104
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
854
1105
  deviceTypes.push(d.deviceType);
@@ -858,9 +1109,12 @@ export class Frontend extends EventEmitter {
858
1109
  return;
859
1110
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
860
1111
  return;
1112
+ // console.log(
1113
+ // `${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}`,
1114
+ // );
861
1115
  clusters.push({
862
1116
  endpoint: childEndpoint.number.toString(),
863
- id: childEndpoint.maybeId ?? 'null',
1117
+ id: childEndpoint.maybeId ?? 'null', // Never happens
864
1118
  deviceTypes,
865
1119
  clusterName: capitalizeFirstLetter(clusterName),
866
1120
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -873,6 +1127,13 @@ export class Frontend extends EventEmitter {
873
1127
  });
874
1128
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
875
1129
  }
1130
+ /**
1131
+ * Handles incoming websocket messages for the Matterbridge frontend.
1132
+ *
1133
+ * @param {WebSocket} client - The websocket client that sent the message.
1134
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1135
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1136
+ */
876
1137
  async wsMessageHandler(client, message) {
877
1138
  let data;
878
1139
  try {
@@ -919,32 +1180,41 @@ export class Frontend extends EventEmitter {
919
1180
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
920
1181
  const packageName = data.params.packageName.replace(/@.*$/, '');
921
1182
  if (data.params.restart === false && packageName !== 'matterbridge') {
1183
+ // The install comes from InstallPlugins
922
1184
  this.matterbridge.plugins
923
1185
  .add(packageName)
924
1186
  .then((plugin) => {
925
1187
  if (plugin) {
1188
+ // The plugin is not registered
926
1189
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
927
1190
  this.matterbridge.plugins
928
1191
  .load(plugin, true, 'The plugin has been added', true)
1192
+ // eslint-disable-next-line promise/no-nesting
929
1193
  .then(() => {
930
1194
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
931
1195
  this.wssSendRefreshRequired('plugins');
932
1196
  return;
933
1197
  })
1198
+ // eslint-disable-next-line promise/no-nesting
934
1199
  .catch((_error) => {
1200
+ //
935
1201
  });
936
1202
  }
937
1203
  else {
1204
+ // The plugin is already registered
938
1205
  this.wssSendSnackbarMessage(`Restart required`, 0);
939
1206
  this.wssSendRefreshRequired('plugins');
940
1207
  this.wssSendRestartRequired();
941
1208
  }
942
1209
  return;
943
1210
  })
1211
+ // eslint-disable-next-line promise/no-nesting
944
1212
  .catch((_error) => {
1213
+ //
945
1214
  });
946
1215
  }
947
1216
  else {
1217
+ // The package is matterbridge
948
1218
  if (this.matterbridge.restartMode !== '') {
949
1219
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
950
1220
  this.matterbridge.shutdownProcess();
@@ -967,6 +1237,7 @@ export class Frontend extends EventEmitter {
967
1237
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
968
1238
  return;
969
1239
  }
1240
+ // The package is a plugin
970
1241
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
971
1242
  if (plugin) {
972
1243
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -975,6 +1246,7 @@ export class Frontend extends EventEmitter {
975
1246
  this.wssSendRefreshRequired('plugins');
976
1247
  this.wssSendRefreshRequired('devices');
977
1248
  }
1249
+ // Uninstall the package
978
1250
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
979
1251
  const { spawnCommand } = await import('./utils/spawn.js');
980
1252
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1015,6 +1287,7 @@ export class Frontend extends EventEmitter {
1015
1287
  return;
1016
1288
  })
1017
1289
  .catch((_error) => {
1290
+ //
1018
1291
  });
1019
1292
  }
1020
1293
  else {
@@ -1061,6 +1334,7 @@ export class Frontend extends EventEmitter {
1061
1334
  return;
1062
1335
  })
1063
1336
  .catch((_error) => {
1337
+ //
1064
1338
  });
1065
1339
  }
1066
1340
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1171,6 +1445,8 @@ export class Frontend extends EventEmitter {
1171
1445
  else if (data.method === '/api/advertise') {
1172
1446
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
1173
1447
  this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
1448
+ // this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
1449
+ // this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
1174
1450
  this.wssSendRefreshRequired('matterbridgeAdvertise');
1175
1451
  this.wssSendSnackbarMessage(`Started fabrics share`, 0);
1176
1452
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
@@ -1293,22 +1569,22 @@ export class Frontend extends EventEmitter {
1293
1569
  if (isValidString(data.params.value, 4)) {
1294
1570
  this.log.debug('Matterbridge logger level:', data.params.value);
1295
1571
  if (data.params.value === 'Debug') {
1296
- await this.matterbridge.setLogLevel("debug");
1572
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1297
1573
  }
1298
1574
  else if (data.params.value === 'Info') {
1299
- await this.matterbridge.setLogLevel("info");
1575
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1300
1576
  }
1301
1577
  else if (data.params.value === 'Notice') {
1302
- await this.matterbridge.setLogLevel("notice");
1578
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1303
1579
  }
1304
1580
  else if (data.params.value === 'Warn') {
1305
- await this.matterbridge.setLogLevel("warn");
1581
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1306
1582
  }
1307
1583
  else if (data.params.value === 'Error') {
1308
- await this.matterbridge.setLogLevel("error");
1584
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1309
1585
  }
1310
1586
  else if (data.params.value === 'Fatal') {
1311
- await this.matterbridge.setLogLevel("fatal");
1587
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1312
1588
  }
1313
1589
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1314
1590
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1319,6 +1595,7 @@ export class Frontend extends EventEmitter {
1319
1595
  this.log.debug('Matterbridge file log:', data.params.value);
1320
1596
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1321
1597
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1598
+ // Create the file logger for matterbridge
1322
1599
  if (data.params.value)
1323
1600
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1324
1601
  else
@@ -1365,6 +1642,7 @@ export class Frontend extends EventEmitter {
1365
1642
  });
1366
1643
  }
1367
1644
  catch (error) {
1645
+ /* istanbul ignore next */
1368
1646
  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}`);
1369
1647
  }
1370
1648
  }
@@ -1373,6 +1651,7 @@ export class Frontend extends EventEmitter {
1373
1651
  Logger.removeLogger('matterfilelogger');
1374
1652
  }
1375
1653
  catch (error) {
1654
+ /* istanbul ignore next */
1376
1655
  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}`);
1377
1656
  }
1378
1657
  }
@@ -1485,15 +1764,19 @@ export class Frontend extends EventEmitter {
1485
1764
  return;
1486
1765
  }
1487
1766
  const config = plugin.configJson;
1767
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1488
1768
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1769
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1489
1770
  if (select === 'serial')
1490
1771
  this.log.info(`Selected device serial ${data.params.serial}`);
1491
1772
  if (select === 'name')
1492
1773
  this.log.info(`Selected device name ${data.params.name}`);
1493
1774
  if (config && select && (select === 'serial' || select === 'name')) {
1775
+ // Remove postfix from the serial if it exists
1494
1776
  if (config.postfix) {
1495
1777
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1496
1778
  }
1779
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1497
1780
  if (isValidArray(config.whiteList, 1)) {
1498
1781
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1499
1782
  config.whiteList.push(data.params.serial);
@@ -1502,6 +1785,7 @@ export class Frontend extends EventEmitter {
1502
1785
  config.whiteList.push(data.params.name);
1503
1786
  }
1504
1787
  }
1788
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1505
1789
  if (isValidArray(config.blackList, 1)) {
1506
1790
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1507
1791
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1532,7 +1816,9 @@ export class Frontend extends EventEmitter {
1532
1816
  return;
1533
1817
  }
1534
1818
  const config = plugin.configJson;
1819
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1535
1820
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1821
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1536
1822
  if (select === 'serial')
1537
1823
  this.log.info(`Unselected device serial ${data.params.serial}`);
1538
1824
  if (select === 'name')
@@ -1541,6 +1827,7 @@ export class Frontend extends EventEmitter {
1541
1827
  if (config.postfix) {
1542
1828
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1543
1829
  }
1830
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1544
1831
  if (isValidArray(config.whiteList, 1)) {
1545
1832
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1546
1833
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1549,6 +1836,7 @@ export class Frontend extends EventEmitter {
1549
1836
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1550
1837
  }
1551
1838
  }
1839
+ // Add the serial to the blackList
1552
1840
  if (isValidArray(config.blackList)) {
1553
1841
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1554
1842
  config.blackList.push(data.params.serial);
@@ -1582,114 +1870,230 @@ export class Frontend extends EventEmitter {
1582
1870
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1583
1871
  }
1584
1872
  }
1873
+ /**
1874
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1875
+ *
1876
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1877
+ * @param {string} time - The time string of the message
1878
+ * @param {string} name - The logger name of the message
1879
+ * @param {string} message - The content of the message.
1880
+ *
1881
+ * @remarks
1882
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1883
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1884
+ * The function sends the message to all connected clients.
1885
+ */
1585
1886
  wssSendMessage(level, time, name, message) {
1586
1887
  if (!level || !time || !name || !message)
1587
1888
  return;
1889
+ // Remove ANSI escape codes from the message
1890
+ // eslint-disable-next-line no-control-regex
1588
1891
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1892
+ // Remove leading asterisks from the message
1589
1893
  message = message.replace(/^\*+/, '');
1894
+ // Replace all occurrences of \t and \n
1590
1895
  message = message.replace(/[\t\n]/g, '');
1896
+ // Remove non-printable characters
1897
+ // eslint-disable-next-line no-control-regex
1591
1898
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1899
+ // Replace all occurrences of \" with "
1592
1900
  message = message.replace(/\\"/g, '"');
1901
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1593
1902
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1903
+ // Define the maximum allowed length for continuous characters without a space
1594
1904
  const maxContinuousLength = 100;
1595
1905
  const keepStartLength = 20;
1596
1906
  const keepEndLength = 20;
1907
+ // Split the message into words
1597
1908
  message = message
1598
1909
  .split(' ')
1599
1910
  .map((word) => {
1911
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1600
1912
  if (word.length > maxContinuousLength) {
1601
1913
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1602
1914
  }
1603
1915
  return word;
1604
1916
  })
1605
1917
  .join(' ');
1918
+ // Send the message to all connected clients
1606
1919
  this.webSocketServer?.clients.forEach((client) => {
1607
1920
  if (client.readyState === WebSocket.OPEN) {
1608
1921
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1609
1922
  }
1610
1923
  });
1611
1924
  }
1925
+ /**
1926
+ * Sends a need to refresh WebSocket message to all connected clients.
1927
+ *
1928
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1929
+ * possible values:
1930
+ * - 'matterbridgeLatestVersion'
1931
+ * - 'matterbridgeAdvertise'
1932
+ * - 'online'
1933
+ * - 'offline'
1934
+ * - 'reachability'
1935
+ * - 'settings'
1936
+ * - 'plugins'
1937
+ * - 'pluginsRestart'
1938
+ * - 'devices'
1939
+ * - 'fabrics'
1940
+ * - 'sessions'
1941
+ */
1612
1942
  wssSendRefreshRequired(changed = null) {
1613
1943
  this.log.debug('Sending a refresh required message to all connected clients');
1944
+ // Send the message to all connected clients
1614
1945
  this.webSocketServer?.clients.forEach((client) => {
1615
1946
  if (client.readyState === WebSocket.OPEN) {
1616
1947
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1617
1948
  }
1618
1949
  });
1619
1950
  }
1951
+ /**
1952
+ * Sends a need to restart WebSocket message to all connected clients.
1953
+ *
1954
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
1955
+ */
1620
1956
  wssSendRestartRequired(snackbar = true) {
1621
1957
  this.log.debug('Sending a restart required message to all connected clients');
1622
1958
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1623
1959
  if (snackbar === true)
1624
1960
  this.wssSendSnackbarMessage(`Restart required`, 0);
1961
+ // Send the message to all connected clients
1625
1962
  this.webSocketServer?.clients.forEach((client) => {
1626
1963
  if (client.readyState === WebSocket.OPEN) {
1627
1964
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1628
1965
  }
1629
1966
  });
1630
1967
  }
1968
+ /**
1969
+ * Sends a need to update WebSocket message to all connected clients.
1970
+ *
1971
+ */
1631
1972
  wssSendUpdateRequired() {
1632
1973
  this.log.debug('Sending an update required message to all connected clients');
1633
1974
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1975
+ // Send the message to all connected clients
1634
1976
  this.webSocketServer?.clients.forEach((client) => {
1635
1977
  if (client.readyState === WebSocket.OPEN) {
1636
1978
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1637
1979
  }
1638
1980
  });
1639
1981
  }
1982
+ /**
1983
+ * Sends a cpu update message to all connected clients.
1984
+ *
1985
+ * @param {number} cpuUsage - The CPU usage percentage to send.
1986
+ */
1640
1987
  wssSendCpuUpdate(cpuUsage) {
1641
1988
  if (hasParameter('debug'))
1642
1989
  this.log.debug('Sending a cpu update message to all connected clients');
1990
+ // Send the message to all connected clients
1643
1991
  this.webSocketServer?.clients.forEach((client) => {
1644
1992
  if (client.readyState === WebSocket.OPEN) {
1645
1993
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1646
1994
  }
1647
1995
  });
1648
1996
  }
1997
+ /**
1998
+ * Sends a memory update message to all connected clients.
1999
+ *
2000
+ * @param {string} totalMemory - The total memory in bytes.
2001
+ * @param {string} freeMemory - The free memory in bytes.
2002
+ * @param {string} rss - The resident set size in bytes.
2003
+ * @param {string} heapTotal - The total heap memory in bytes.
2004
+ * @param {string} heapUsed - The used heap memory in bytes.
2005
+ * @param {string} external - The external memory in bytes.
2006
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2007
+ */
1649
2008
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1650
2009
  if (hasParameter('debug'))
1651
2010
  this.log.debug('Sending a memory update message to all connected clients');
2011
+ // Send the message to all connected clients
1652
2012
  this.webSocketServer?.clients.forEach((client) => {
1653
2013
  if (client.readyState === WebSocket.OPEN) {
1654
2014
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1655
2015
  }
1656
2016
  });
1657
2017
  }
2018
+ /**
2019
+ * Sends an uptime update message to all connected clients.
2020
+ *
2021
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2022
+ * @param {string} processUptime - The process uptime in a human-readable format.
2023
+ */
1658
2024
  wssSendUptimeUpdate(systemUptime, processUptime) {
1659
2025
  if (hasParameter('debug'))
1660
2026
  this.log.debug('Sending a uptime update message to all connected clients');
2027
+ // Send the message to all connected clients
1661
2028
  this.webSocketServer?.clients.forEach((client) => {
1662
2029
  if (client.readyState === WebSocket.OPEN) {
1663
2030
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1664
2031
  }
1665
2032
  });
1666
2033
  }
2034
+ /**
2035
+ * Sends an open snackbar message to all connected clients.
2036
+ *
2037
+ * @param {string} message - The message to send.
2038
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
2039
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2040
+ */
1667
2041
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1668
2042
  this.log.debug('Sending a snackbar message to all connected clients');
2043
+ // Send the message to all connected clients
1669
2044
  this.webSocketServer?.clients.forEach((client) => {
1670
2045
  if (client.readyState === WebSocket.OPEN) {
1671
2046
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1672
2047
  }
1673
2048
  });
1674
2049
  }
2050
+ /**
2051
+ * Sends a close snackbar message to all connected clients.
2052
+ *
2053
+ * @param {string} message - The message to send.
2054
+ */
1675
2055
  wssSendCloseSnackbarMessage(message) {
1676
2056
  this.log.debug('Sending a close snackbar message to all connected clients');
2057
+ // Send the message to all connected clients
1677
2058
  this.webSocketServer?.clients.forEach((client) => {
1678
2059
  if (client.readyState === WebSocket.OPEN) {
1679
2060
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1680
2061
  }
1681
2062
  });
1682
2063
  }
2064
+ /**
2065
+ * Sends an attribute update message to all connected WebSocket clients.
2066
+ *
2067
+ * @param {string | undefined} plugin - The name of the plugin.
2068
+ * @param {string | undefined} serialNumber - The serial number of the device.
2069
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2070
+ * @param {string} cluster - The cluster name where the attribute belongs.
2071
+ * @param {string} attribute - The name of the attribute that changed.
2072
+ * @param {number | string | boolean} value - The new value of the attribute.
2073
+ *
2074
+ * @remarks
2075
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2076
+ * with the updated attribute information.
2077
+ */
1683
2078
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1684
2079
  this.log.debug('Sending an attribute update message to all connected clients');
2080
+ // Send the message to all connected clients
1685
2081
  this.webSocketServer?.clients.forEach((client) => {
1686
2082
  if (client.readyState === WebSocket.OPEN) {
1687
2083
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1688
2084
  }
1689
2085
  });
1690
2086
  }
2087
+ /**
2088
+ * Sends a message to all connected clients.
2089
+ *
2090
+ * @param {number} id - The message id.
2091
+ * @param {string} method - The message method.
2092
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
2093
+ */
1691
2094
  wssBroadcastMessage(id, method, params) {
1692
2095
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2096
+ // Send the message to all connected clients
1693
2097
  this.webSocketServer?.clients.forEach((client) => {
1694
2098
  if (client.readyState === WebSocket.OPEN) {
1695
2099
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1697,3 +2101,4 @@ export class Frontend extends EventEmitter {
1697
2101
  });
1698
2102
  }
1699
2103
  }
2104
+ //# sourceMappingURL=frontend.js.map