matterbridge 3.3.8-dev-20251115-ca5ff21 → 3.3.8

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 (310) hide show
  1. package/dist/broadcastServer.d.ts +115 -0
  2. package/dist/broadcastServer.d.ts.map +1 -0
  3. package/dist/broadcastServer.js +93 -1
  4. package/dist/broadcastServer.js.map +1 -0
  5. package/dist/broadcastServerTypes.d.ts +806 -0
  6. package/dist/broadcastServerTypes.d.ts.map +1 -0
  7. package/dist/broadcastServerTypes.js +24 -0
  8. package/dist/broadcastServerTypes.js.map +1 -0
  9. package/dist/cli.d.ts +30 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +97 -1
  12. package/dist/cli.js.map +1 -0
  13. package/dist/cliEmitter.d.ts +50 -0
  14. package/dist/cliEmitter.d.ts.map +1 -0
  15. package/dist/cliEmitter.js +37 -0
  16. package/dist/cliEmitter.js.map +1 -0
  17. package/dist/cliHistory.d.ts +48 -0
  18. package/dist/cliHistory.d.ts.map +1 -0
  19. package/dist/cliHistory.js +38 -0
  20. package/dist/cliHistory.js.map +1 -0
  21. package/dist/clusters/export.d.ts +2 -0
  22. package/dist/clusters/export.d.ts.map +1 -0
  23. package/dist/clusters/export.js +2 -0
  24. package/dist/clusters/export.js.map +1 -0
  25. package/dist/defaultConfigSchema.d.ts +28 -0
  26. package/dist/defaultConfigSchema.d.ts.map +1 -0
  27. package/dist/defaultConfigSchema.js +24 -0
  28. package/dist/defaultConfigSchema.js.map +1 -0
  29. package/dist/deviceManager.d.ts +128 -0
  30. package/dist/deviceManager.d.ts.map +1 -0
  31. package/dist/deviceManager.js +105 -1
  32. package/dist/deviceManager.js.map +1 -0
  33. package/dist/devices/airConditioner.d.ts +98 -0
  34. package/dist/devices/airConditioner.d.ts.map +1 -0
  35. package/dist/devices/airConditioner.js +57 -0
  36. package/dist/devices/airConditioner.js.map +1 -0
  37. package/dist/devices/batteryStorage.d.ts +48 -0
  38. package/dist/devices/batteryStorage.d.ts.map +1 -0
  39. package/dist/devices/batteryStorage.js +48 -1
  40. package/dist/devices/batteryStorage.js.map +1 -0
  41. package/dist/devices/cooktop.d.ts +60 -0
  42. package/dist/devices/cooktop.d.ts.map +1 -0
  43. package/dist/devices/cooktop.js +55 -0
  44. package/dist/devices/cooktop.js.map +1 -0
  45. package/dist/devices/dishwasher.d.ts +71 -0
  46. package/dist/devices/dishwasher.d.ts.map +1 -0
  47. package/dist/devices/dishwasher.js +57 -0
  48. package/dist/devices/dishwasher.js.map +1 -0
  49. package/dist/devices/evse.d.ts +76 -0
  50. package/dist/devices/evse.d.ts.map +1 -0
  51. package/dist/devices/evse.js +74 -10
  52. package/dist/devices/evse.js.map +1 -0
  53. package/dist/devices/export.d.ts +17 -0
  54. package/dist/devices/export.d.ts.map +1 -0
  55. package/dist/devices/export.js +5 -0
  56. package/dist/devices/export.js.map +1 -0
  57. package/dist/devices/extractorHood.d.ts +46 -0
  58. package/dist/devices/extractorHood.d.ts.map +1 -0
  59. package/dist/devices/extractorHood.js +43 -0
  60. package/dist/devices/extractorHood.js.map +1 -0
  61. package/dist/devices/heatPump.d.ts +47 -0
  62. package/dist/devices/heatPump.d.ts.map +1 -0
  63. package/dist/devices/heatPump.js +50 -2
  64. package/dist/devices/heatPump.js.map +1 -0
  65. package/dist/devices/laundryDryer.d.ts +67 -0
  66. package/dist/devices/laundryDryer.d.ts.map +1 -0
  67. package/dist/devices/laundryDryer.js +62 -3
  68. package/dist/devices/laundryDryer.js.map +1 -0
  69. package/dist/devices/laundryWasher.d.ts +81 -0
  70. package/dist/devices/laundryWasher.d.ts.map +1 -0
  71. package/dist/devices/laundryWasher.js +70 -4
  72. package/dist/devices/laundryWasher.js.map +1 -0
  73. package/dist/devices/microwaveOven.d.ts +168 -0
  74. package/dist/devices/microwaveOven.d.ts.map +1 -0
  75. package/dist/devices/microwaveOven.js +88 -5
  76. package/dist/devices/microwaveOven.js.map +1 -0
  77. package/dist/devices/oven.d.ts +105 -0
  78. package/dist/devices/oven.d.ts.map +1 -0
  79. package/dist/devices/oven.js +92 -11
  80. package/dist/devices/oven.js.map +1 -0
  81. package/dist/devices/refrigerator.d.ts +118 -0
  82. package/dist/devices/refrigerator.d.ts.map +1 -0
  83. package/dist/devices/refrigerator.js +102 -0
  84. package/dist/devices/refrigerator.js.map +1 -0
  85. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  86. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  87. package/dist/devices/roboticVacuumCleaner.js +111 -20
  88. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  89. package/dist/devices/solarPower.d.ts +40 -0
  90. package/dist/devices/solarPower.d.ts.map +1 -0
  91. package/dist/devices/solarPower.js +38 -0
  92. package/dist/devices/solarPower.js.map +1 -0
  93. package/dist/devices/speaker.d.ts +87 -0
  94. package/dist/devices/speaker.d.ts.map +1 -0
  95. package/dist/devices/speaker.js +84 -0
  96. package/dist/devices/speaker.js.map +1 -0
  97. package/dist/devices/temperatureControl.d.ts +166 -0
  98. package/dist/devices/temperatureControl.d.ts.map +1 -0
  99. package/dist/devices/temperatureControl.js +24 -3
  100. package/dist/devices/temperatureControl.js.map +1 -0
  101. package/dist/devices/waterHeater.d.ts +111 -0
  102. package/dist/devices/waterHeater.d.ts.map +1 -0
  103. package/dist/devices/waterHeater.js +82 -2
  104. package/dist/devices/waterHeater.js.map +1 -0
  105. package/dist/dgram/coap.d.ts +205 -0
  106. package/dist/dgram/coap.d.ts.map +1 -0
  107. package/dist/dgram/coap.js +126 -13
  108. package/dist/dgram/coap.js.map +1 -0
  109. package/dist/dgram/dgram.d.ts +141 -0
  110. package/dist/dgram/dgram.d.ts.map +1 -0
  111. package/dist/dgram/dgram.js +114 -2
  112. package/dist/dgram/dgram.js.map +1 -0
  113. package/dist/dgram/mb_coap.d.ts +24 -0
  114. package/dist/dgram/mb_coap.d.ts.map +1 -0
  115. package/dist/dgram/mb_coap.js +41 -3
  116. package/dist/dgram/mb_coap.js.map +1 -0
  117. package/dist/dgram/mb_mdns.d.ts +24 -0
  118. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  119. package/dist/dgram/mb_mdns.js +80 -15
  120. package/dist/dgram/mb_mdns.js.map +1 -0
  121. package/dist/dgram/mdns.d.ts +290 -0
  122. package/dist/dgram/mdns.d.ts.map +1 -0
  123. package/dist/dgram/mdns.js +299 -137
  124. package/dist/dgram/mdns.js.map +1 -0
  125. package/dist/dgram/multicast.d.ts +67 -0
  126. package/dist/dgram/multicast.d.ts.map +1 -0
  127. package/dist/dgram/multicast.js +62 -1
  128. package/dist/dgram/multicast.js.map +1 -0
  129. package/dist/dgram/unicast.d.ts +56 -0
  130. package/dist/dgram/unicast.d.ts.map +1 -0
  131. package/dist/dgram/unicast.js +54 -0
  132. package/dist/dgram/unicast.js.map +1 -0
  133. package/dist/frontend.d.ts +238 -0
  134. package/dist/frontend.d.ts.map +1 -0
  135. package/dist/frontend.js +454 -35
  136. package/dist/frontend.js.map +1 -0
  137. package/dist/frontendTypes.d.ts +529 -0
  138. package/dist/frontendTypes.d.ts.map +1 -0
  139. package/dist/frontendTypes.js +45 -0
  140. package/dist/frontendTypes.js.map +1 -0
  141. package/dist/helpers.d.ts +48 -0
  142. package/dist/helpers.d.ts.map +1 -0
  143. package/dist/helpers.js +53 -0
  144. package/dist/helpers.js.map +1 -0
  145. package/dist/index.d.ts +34 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +25 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/jestutils/export.d.ts +2 -0
  150. package/dist/jestutils/export.d.ts.map +1 -0
  151. package/dist/jestutils/export.js +1 -0
  152. package/dist/jestutils/export.js.map +1 -0
  153. package/dist/jestutils/jestHelpers.d.ts +250 -0
  154. package/dist/jestutils/jestHelpers.d.ts.map +1 -0
  155. package/dist/jestutils/jestHelpers.js +275 -4
  156. package/dist/jestutils/jestHelpers.js.map +1 -0
  157. package/dist/logger/export.d.ts +2 -0
  158. package/dist/logger/export.d.ts.map +1 -0
  159. package/dist/logger/export.js +1 -0
  160. package/dist/logger/export.js.map +1 -0
  161. package/dist/matter/behaviors.d.ts +2 -0
  162. package/dist/matter/behaviors.d.ts.map +1 -0
  163. package/dist/matter/behaviors.js +2 -0
  164. package/dist/matter/behaviors.js.map +1 -0
  165. package/dist/matter/clusters.d.ts +2 -0
  166. package/dist/matter/clusters.d.ts.map +1 -0
  167. package/dist/matter/clusters.js +2 -0
  168. package/dist/matter/clusters.js.map +1 -0
  169. package/dist/matter/devices.d.ts +2 -0
  170. package/dist/matter/devices.d.ts.map +1 -0
  171. package/dist/matter/devices.js +2 -0
  172. package/dist/matter/devices.js.map +1 -0
  173. package/dist/matter/endpoints.d.ts +2 -0
  174. package/dist/matter/endpoints.d.ts.map +1 -0
  175. package/dist/matter/endpoints.js +2 -0
  176. package/dist/matter/endpoints.js.map +1 -0
  177. package/dist/matter/export.d.ts +5 -0
  178. package/dist/matter/export.d.ts.map +1 -0
  179. package/dist/matter/export.js +3 -0
  180. package/dist/matter/export.js.map +1 -0
  181. package/dist/matter/types.d.ts +3 -0
  182. package/dist/matter/types.d.ts.map +1 -0
  183. package/dist/matter/types.js +3 -0
  184. package/dist/matter/types.js.map +1 -0
  185. package/dist/matterbridge.d.ts +469 -0
  186. package/dist/matterbridge.d.ts.map +1 -0
  187. package/dist/matterbridge.js +788 -46
  188. package/dist/matterbridge.js.map +1 -0
  189. package/dist/matterbridgeAccessoryPlatform.d.ts +41 -0
  190. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  191. package/dist/matterbridgeAccessoryPlatform.js +38 -0
  192. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  193. package/dist/matterbridgeBehaviors.d.ts +2404 -0
  194. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  195. package/dist/matterbridgeBehaviors.js +77 -14
  196. package/dist/matterbridgeBehaviors.js.map +1 -0
  197. package/dist/matterbridgeDeviceTypes.d.ts +698 -0
  198. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  199. package/dist/matterbridgeDeviceTypes.js +635 -14
  200. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  201. package/dist/matterbridgeDynamicPlatform.d.ts +41 -0
  202. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  203. package/dist/matterbridgeDynamicPlatform.js +38 -0
  204. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  205. package/dist/matterbridgeEndpoint.d.ts +1490 -0
  206. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  207. package/dist/matterbridgeEndpoint.js +1432 -53
  208. package/dist/matterbridgeEndpoint.js.map +1 -0
  209. package/dist/matterbridgeEndpointHelpers.d.ts +787 -0
  210. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  211. package/dist/matterbridgeEndpointHelpers.js +488 -25
  212. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  213. package/dist/matterbridgeEndpointTypes.d.ts +197 -0
  214. package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
  215. package/dist/matterbridgeEndpointTypes.js +25 -0
  216. package/dist/matterbridgeEndpointTypes.js.map +1 -0
  217. package/dist/matterbridgePlatform.d.ts +415 -0
  218. package/dist/matterbridgePlatform.d.ts.map +1 -0
  219. package/dist/matterbridgePlatform.js +354 -1
  220. package/dist/matterbridgePlatform.js.map +1 -0
  221. package/dist/matterbridgeTypes.d.ts +239 -0
  222. package/dist/matterbridgeTypes.d.ts.map +1 -0
  223. package/dist/matterbridgeTypes.js +26 -0
  224. package/dist/matterbridgeTypes.js.map +1 -0
  225. package/dist/pluginManager.d.ts +371 -0
  226. package/dist/pluginManager.d.ts.map +1 -0
  227. package/dist/pluginManager.js +339 -4
  228. package/dist/pluginManager.js.map +1 -0
  229. package/dist/shelly.d.ts +174 -0
  230. package/dist/shelly.d.ts.map +1 -0
  231. package/dist/shelly.js +168 -7
  232. package/dist/shelly.js.map +1 -0
  233. package/dist/storage/export.d.ts +2 -0
  234. package/dist/storage/export.d.ts.map +1 -0
  235. package/dist/storage/export.js +1 -0
  236. package/dist/storage/export.js.map +1 -0
  237. package/dist/update.d.ts +75 -0
  238. package/dist/update.d.ts.map +1 -0
  239. package/dist/update.js +69 -0
  240. package/dist/update.js.map +1 -0
  241. package/dist/utils/colorUtils.d.ts +101 -0
  242. package/dist/utils/colorUtils.d.ts.map +1 -0
  243. package/dist/utils/colorUtils.js +97 -2
  244. package/dist/utils/colorUtils.js.map +1 -0
  245. package/dist/utils/commandLine.d.ts +66 -0
  246. package/dist/utils/commandLine.d.ts.map +1 -0
  247. package/dist/utils/commandLine.js +60 -0
  248. package/dist/utils/commandLine.js.map +1 -0
  249. package/dist/utils/copyDirectory.d.ts +33 -0
  250. package/dist/utils/copyDirectory.d.ts.map +1 -0
  251. package/dist/utils/copyDirectory.js +38 -1
  252. package/dist/utils/copyDirectory.js.map +1 -0
  253. package/dist/utils/createDirectory.d.ts +34 -0
  254. package/dist/utils/createDirectory.d.ts.map +1 -0
  255. package/dist/utils/createDirectory.js +33 -0
  256. package/dist/utils/createDirectory.js.map +1 -0
  257. package/dist/utils/createZip.d.ts +39 -0
  258. package/dist/utils/createZip.d.ts.map +1 -0
  259. package/dist/utils/createZip.js +47 -2
  260. package/dist/utils/createZip.js.map +1 -0
  261. package/dist/utils/deepCopy.d.ts +32 -0
  262. package/dist/utils/deepCopy.d.ts.map +1 -0
  263. package/dist/utils/deepCopy.js +39 -0
  264. package/dist/utils/deepCopy.js.map +1 -0
  265. package/dist/utils/deepEqual.d.ts +54 -0
  266. package/dist/utils/deepEqual.d.ts.map +1 -0
  267. package/dist/utils/deepEqual.js +72 -1
  268. package/dist/utils/deepEqual.js.map +1 -0
  269. package/dist/utils/error.d.ts +44 -0
  270. package/dist/utils/error.d.ts.map +1 -0
  271. package/dist/utils/error.js +41 -0
  272. package/dist/utils/error.js.map +1 -0
  273. package/dist/utils/export.d.ts +13 -0
  274. package/dist/utils/export.d.ts.map +1 -0
  275. package/dist/utils/export.js +1 -0
  276. package/dist/utils/export.js.map +1 -0
  277. package/dist/utils/format.d.ts +53 -0
  278. package/dist/utils/format.d.ts.map +1 -0
  279. package/dist/utils/format.js +49 -0
  280. package/dist/utils/format.js.map +1 -0
  281. package/dist/utils/hex.d.ts +89 -0
  282. package/dist/utils/hex.d.ts.map +1 -0
  283. package/dist/utils/hex.js +124 -0
  284. package/dist/utils/hex.js.map +1 -0
  285. package/dist/utils/inspector.d.ts +87 -0
  286. package/dist/utils/inspector.d.ts.map +1 -0
  287. package/dist/utils/inspector.js +69 -1
  288. package/dist/utils/inspector.js.map +1 -0
  289. package/dist/utils/isvalid.d.ts +103 -0
  290. package/dist/utils/isvalid.d.ts.map +1 -0
  291. package/dist/utils/isvalid.js +101 -0
  292. package/dist/utils/isvalid.js.map +1 -0
  293. package/dist/utils/network.d.ts +101 -0
  294. package/dist/utils/network.d.ts.map +1 -0
  295. package/dist/utils/network.js +96 -5
  296. package/dist/utils/network.js.map +1 -0
  297. package/dist/utils/spawn.d.ts +35 -0
  298. package/dist/utils/spawn.d.ts.map +1 -0
  299. package/dist/utils/spawn.js +71 -0
  300. package/dist/utils/spawn.js.map +1 -0
  301. package/dist/utils/tracker.d.ts +108 -0
  302. package/dist/utils/tracker.d.ts.map +1 -0
  303. package/dist/utils/tracker.js +64 -1
  304. package/dist/utils/tracker.js.map +1 -0
  305. package/dist/utils/wait.d.ts +54 -0
  306. package/dist/utils/wait.d.ts.map +1 -0
  307. package/dist/utils/wait.js +60 -8
  308. package/dist/utils/wait.js.map +1 -0
  309. package/npm-shrinkwrap.json +2 -2
  310. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,8 +1,34 @@
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.3.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
+ // eslint-disable-next-line no-console
1
25
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
26
  console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
27
+ // Node modules
3
28
  import os from 'node:os';
4
29
  import path from 'node:path';
5
30
  import EventEmitter from 'node:events';
31
+ // AnsiLogger module
6
32
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
7
33
  import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
8
34
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
@@ -37,7 +63,7 @@ export class Frontend extends EventEmitter {
37
63
  constructor(matterbridge) {
38
64
  super();
39
65
  this.matterbridge = matterbridge;
40
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
66
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
41
67
  this.log.logNameColor = '\x1b[38;5;97m';
42
68
  this.server = new BroadcastServer('frontend', this.log);
43
69
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -136,23 +162,53 @@ export class Frontend extends EventEmitter {
136
162
  this.port = port;
137
163
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
138
164
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
165
+ // Initialize multer with the upload directory
139
166
  const multer = await import('multer');
140
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
167
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
141
168
  const upload = multer.default({ dest: uploadDir });
169
+ // Create the express app that serves the frontend
142
170
  const express = await import('express');
143
171
  this.expressApp = express.default();
172
+ // Inject logging/debug wrapper for route/middleware registration
173
+ /*
174
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
175
+ for (const method of methods) {
176
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
177
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
180
+ try {
181
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
182
+ return original(path, ...rest);
183
+ } catch (err) {
184
+ console.error(`[ERROR] Failed to register route: ${path}`);
185
+ throw err;
186
+ }
187
+ };
188
+ }
189
+ */
190
+ // Log all requests to the server for debugging
191
+ /*
192
+ this.expressApp.use((req, res, next) => {
193
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
194
+ next();
195
+ });
196
+ */
197
+ // Serve static files from 'frontend/build' directory
144
198
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
199
+ // Create a WebSocket server and attach it to the http or https server
145
200
  this.log.debug(`Creating WebSocketServer...`);
146
201
  const ws = await import('ws');
147
202
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
148
203
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
149
204
  this.webSocketServer.on('connection', (ws, request) => {
150
205
  const clientIp = request.socket.remoteAddress;
151
- let callbackLogLevel = "notice";
152
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
153
- callbackLogLevel = "info";
154
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
155
- callbackLogLevel = "debug";
206
+ // Set the global logger callback for the WebSocketServer
207
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
208
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
209
+ callbackLogLevel = "info" /* LogLevel.INFO */;
210
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
211
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
156
212
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
157
213
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
158
214
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -174,16 +230,25 @@ export class Frontend extends EventEmitter {
174
230
  }
175
231
  });
176
232
  ws.on('error', (error) => {
233
+ // istanbul ignore next
177
234
  this.log.error(`WebSocket client error: ${error}`);
178
235
  });
179
236
  });
180
237
  this.webSocketServer.on('close', () => {
181
238
  this.log.debug(`WebSocketServer closed`);
182
239
  });
240
+ /* With { noServer: true } it never fires
241
+ this.webSocketServer.on('listening', () => {
242
+ this.log.info(`The WebSocketServer is listening`);
243
+ this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
244
+ });
245
+ */
246
+ // istanbul ignore next
183
247
  this.webSocketServer.on('error', (ws, error) => {
184
248
  this.log.error(`WebSocketServer error: ${error}`);
185
249
  });
186
250
  if (!hasParameter('ssl')) {
251
+ // Create an HTTP server and attach the express app
187
252
  const http = await import('node:http');
188
253
  try {
189
254
  this.log.debug(`Creating HTTP server...`);
@@ -194,6 +259,7 @@ export class Frontend extends EventEmitter {
194
259
  this.emit('server_error', error);
195
260
  return;
196
261
  }
262
+ // Listen on the specified port
197
263
  if (hasParameter('ingress')) {
198
264
  this.httpServer.listen(this.port, '0.0.0.0', () => {
199
265
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -213,23 +279,29 @@ export class Frontend extends EventEmitter {
213
279
  }
214
280
  this.httpServer.on('upgrade', async (req, socket, head) => {
215
281
  try {
282
+ // Only proceed for real WebSocket upgrades
283
+ // istanbul ignore next cause is only a safety check
216
284
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
217
285
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
218
286
  return socket.destroy();
219
287
  }
288
+ // Build a URL so we can read ?password=...
220
289
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
290
+ // Validate WebSocket password
221
291
  const password = url.searchParams.get('password') ?? '';
222
292
  if (password !== this.storedPassword) {
223
293
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
224
294
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
225
295
  return socket.destroy();
226
296
  }
297
+ // Complete the WebSocket handshake
227
298
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
228
299
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
229
300
  this.webSocketServer?.emit('connection', ws, req);
230
301
  });
231
302
  }
232
303
  catch (err) {
304
+ /* istanbul ignore next: only triggered on unexpected internal error */
233
305
  {
234
306
  inspectError(this.log, 'WebSocket upgrade error:', err);
235
307
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -252,6 +324,7 @@ export class Frontend extends EventEmitter {
252
324
  });
253
325
  }
254
326
  else {
327
+ // SSL is enabled, load the certificate and the private key
255
328
  let cert;
256
329
  let key;
257
330
  let ca;
@@ -261,6 +334,7 @@ export class Frontend extends EventEmitter {
261
334
  let httpsServerOptions = {};
262
335
  const fs = await import('node:fs');
263
336
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
337
+ // Load the p12 certificate and the passphrase
264
338
  try {
265
339
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
266
340
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -272,7 +346,7 @@ export class Frontend extends EventEmitter {
272
346
  }
273
347
  try {
274
348
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
275
- passphrase = passphrase.trim();
349
+ passphrase = passphrase.trim(); // Ensure no extra characters
276
350
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
277
351
  }
278
352
  catch (error) {
@@ -283,6 +357,7 @@ export class Frontend extends EventEmitter {
283
357
  httpsServerOptions = { pfx, passphrase };
284
358
  }
285
359
  else {
360
+ // Load the SSL certificate, the private key and optionally the CA certificate. If the CA certificate is present, it will be used to create a full chain certificate.
286
361
  try {
287
362
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
288
363
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -312,9 +387,10 @@ export class Frontend extends EventEmitter {
312
387
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
313
388
  }
314
389
  if (hasParameter('mtls')) {
315
- httpsServerOptions.requestCert = true;
316
- httpsServerOptions.rejectUnauthorized = true;
390
+ httpsServerOptions.requestCert = true; // Request client certificate
391
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
317
392
  }
393
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
318
394
  const https = await import('node:https');
319
395
  try {
320
396
  this.log.debug(`Creating HTTPS server...`);
@@ -325,6 +401,7 @@ export class Frontend extends EventEmitter {
325
401
  this.emit('server_error', error);
326
402
  return;
327
403
  }
404
+ // Listen on the specified port
328
405
  if (hasParameter('ingress')) {
329
406
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
330
407
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -344,23 +421,29 @@ export class Frontend extends EventEmitter {
344
421
  }
345
422
  this.httpsServer.on('upgrade', async (req, socket, head) => {
346
423
  try {
424
+ // Only proceed for real WebSocket upgrades
425
+ // istanbul ignore next cause is only a safety check
347
426
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
348
427
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
349
428
  return socket.destroy();
350
429
  }
430
+ // Build a URL so we can read ?password=...
351
431
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
432
+ // Validate WebSocket password
352
433
  const password = url.searchParams.get('password') ?? '';
353
434
  if (password !== this.storedPassword) {
354
435
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
355
436
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
356
437
  return socket.destroy();
357
438
  }
439
+ // Complete the WebSocket handshake
358
440
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
359
441
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
360
442
  this.webSocketServer?.emit('connection', ws, req);
361
443
  });
362
444
  }
363
445
  catch (err) {
446
+ /* istanbul ignore next: only triggered on unexpected internal error */
364
447
  {
365
448
  inspectError(this.log, 'WebSocket upgrade error:', err);
366
449
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -382,6 +465,7 @@ export class Frontend extends EventEmitter {
382
465
  return;
383
466
  });
384
467
  }
468
+ // Subscribe to cli events
385
469
  cliEmitter.removeAllListeners();
386
470
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
387
471
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -392,6 +476,8 @@ export class Frontend extends EventEmitter {
392
476
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
393
477
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
394
478
  });
479
+ // Endpoint to validate login code
480
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
395
481
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
396
482
  const { password } = req.body;
397
483
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -404,17 +490,20 @@ export class Frontend extends EventEmitter {
404
490
  res.json({ valid: false });
405
491
  }
406
492
  });
493
+ // Endpoint to provide health check for docker
407
494
  this.expressApp.get('/health', (req, res) => {
408
495
  this.log.debug('Express received /health');
409
496
  const healthStatus = {
410
- status: 'ok',
411
- uptime: process.uptime(),
412
- timestamp: new Date().toISOString(),
497
+ status: 'ok', // Indicate service is healthy
498
+ uptime: process.uptime(), // Server uptime in seconds
499
+ timestamp: new Date().toISOString(), // Current timestamp
413
500
  };
414
501
  res.status(200).json(healthStatus);
415
502
  });
503
+ // Endpoint to provide memory usage details
416
504
  this.expressApp.get('/memory', async (req, res) => {
417
505
  this.log.debug('Express received /memory');
506
+ // Memory usage from process
418
507
  const memoryUsageRaw = process.memoryUsage();
419
508
  const memoryUsage = {
420
509
  rss: formatBytes(memoryUsageRaw.rss),
@@ -423,10 +512,13 @@ export class Frontend extends EventEmitter {
423
512
  external: formatBytes(memoryUsageRaw.external),
424
513
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
425
514
  };
515
+ // V8 heap statistics
426
516
  const { default: v8 } = await import('node:v8');
427
517
  const heapStatsRaw = v8.getHeapStatistics();
428
518
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
519
+ // Format heapStats
429
520
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
521
+ // Format heapSpaces
430
522
  const heapSpaces = heapSpacesRaw.map((space) => ({
431
523
  ...space,
432
524
  space_size: formatBytes(space.space_size),
@@ -445,18 +537,22 @@ export class Frontend extends EventEmitter {
445
537
  };
446
538
  res.status(200).json(memoryReport);
447
539
  });
540
+ // Endpoint to provide settings
448
541
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
449
542
  this.log.debug('The frontend sent /api/settings');
450
543
  res.json(await this.getApiSettings());
451
544
  });
545
+ // Endpoint to provide plugins
452
546
  this.expressApp.get('/api/plugins', async (req, res) => {
453
547
  this.log.debug('The frontend sent /api/plugins');
454
548
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
455
549
  });
550
+ // Endpoint to provide devices
456
551
  this.expressApp.get('/api/devices', async (req, res) => {
457
552
  this.log.debug('The frontend sent /api/devices');
458
553
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
459
554
  });
555
+ // Endpoint to view the matterbridge log
460
556
  this.expressApp.get('/api/view-mblog', async (req, res) => {
461
557
  this.log.debug('The frontend sent /api/view-mblog');
462
558
  try {
@@ -470,6 +566,7 @@ export class Frontend extends EventEmitter {
470
566
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
471
567
  }
472
568
  });
569
+ // Endpoint to view the matter.js log
473
570
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
474
571
  this.log.debug('The frontend sent /api/view-mjlog');
475
572
  try {
@@ -483,6 +580,7 @@ export class Frontend extends EventEmitter {
483
580
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
484
581
  }
485
582
  });
583
+ // Endpoint to view the diagnostic.log
486
584
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
487
585
  this.log.debug('The frontend sent /api/view-diagnostic');
488
586
  await this.generateDiagnostic();
@@ -493,10 +591,13 @@ export class Frontend extends EventEmitter {
493
591
  res.send(data.slice(29));
494
592
  }
495
593
  catch (error) {
594
+ // istanbul ignore next
496
595
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
596
+ // istanbul ignore next
497
597
  res.status(500).send('Error reading diagnostic log file.');
498
598
  }
499
599
  });
600
+ // Endpoint to download the diagnostic.log
500
601
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
501
602
  this.log.debug(`The frontend sent /api/download-diagnostic`);
502
603
  await this.generateDiagnostic();
@@ -507,16 +608,19 @@ export class Frontend extends EventEmitter {
507
608
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
508
609
  }
509
610
  catch (error) {
611
+ // istanbul ignore next
510
612
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
511
613
  }
512
614
  res.type('text/plain');
513
615
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
616
+ /* istanbul ignore if */
514
617
  if (error) {
515
618
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
516
619
  res.status(500).send('Error downloading the diagnostic log file');
517
620
  }
518
621
  });
519
622
  });
623
+ // Endpoint to view the history.html
520
624
  this.expressApp.get('/api/viewhistory', async (req, res) => {
521
625
  this.log.debug('The frontend sent /api/viewhistory');
522
626
  try {
@@ -530,6 +634,7 @@ export class Frontend extends EventEmitter {
530
634
  res.status(500).send('Error reading history file.');
531
635
  }
532
636
  });
637
+ // Endpoint to download the history.html
533
638
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
534
639
  this.log.debug(`The frontend sent /api/downloadhistory`);
535
640
  try {
@@ -539,6 +644,7 @@ export class Frontend extends EventEmitter {
539
644
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
540
645
  res.type('text/plain');
541
646
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
647
+ /* istanbul ignore if */
542
648
  if (error) {
543
649
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
544
650
  res.status(500).send('Error downloading history file');
@@ -550,6 +656,7 @@ export class Frontend extends EventEmitter {
550
656
  res.status(500).send('Error reading history file.');
551
657
  }
552
658
  });
659
+ // Endpoint to view the shelly log
553
660
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
554
661
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
555
662
  try {
@@ -563,6 +670,7 @@ export class Frontend extends EventEmitter {
563
670
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
564
671
  }
565
672
  });
673
+ // Endpoint to download the matterbridge log
566
674
  this.expressApp.get('/api/download-mblog', async (req, res) => {
567
675
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
568
676
  const fs = await import('node:fs');
@@ -577,12 +685,14 @@ export class Frontend extends EventEmitter {
577
685
  }
578
686
  res.type('text/plain');
579
687
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
688
+ /* istanbul ignore if */
580
689
  if (error) {
581
690
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
582
691
  res.status(500).send('Error downloading the matterbridge log file');
583
692
  }
584
693
  });
585
694
  });
695
+ // Endpoint to download the matter log
586
696
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
587
697
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
588
698
  const fs = await import('node:fs');
@@ -597,12 +707,14 @@ export class Frontend extends EventEmitter {
597
707
  }
598
708
  res.type('text/plain');
599
709
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
710
+ /* istanbul ignore if */
600
711
  if (error) {
601
712
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
602
713
  res.status(500).send('Error downloading the matter log file');
603
714
  }
604
715
  });
605
716
  });
717
+ // Endpoint to download the shelly log
606
718
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
607
719
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
608
720
  const fs = await import('node:fs');
@@ -617,75 +729,91 @@ export class Frontend extends EventEmitter {
617
729
  }
618
730
  res.type('text/plain');
619
731
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
732
+ /* istanbul ignore if */
620
733
  if (error) {
621
734
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
622
735
  res.status(500).send('Error downloading Shelly system log file');
623
736
  }
624
737
  });
625
738
  });
739
+ // Endpoint to download the matterbridge storage directory
626
740
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
627
741
  this.log.debug('The frontend sent /api/download-mbstorage');
628
742
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
629
743
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
744
+ /* istanbul ignore if */
630
745
  if (error) {
631
746
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
632
747
  res.status(500).send('Error downloading the matterbridge storage file');
633
748
  }
634
749
  });
635
750
  });
751
+ // Endpoint to download the matter storage file
636
752
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
637
753
  this.log.debug('The frontend sent /api/download-mjstorage');
638
754
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
639
755
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
756
+ /* istanbul ignore if */
640
757
  if (error) {
641
758
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
642
759
  res.status(500).send('Error downloading the matter storage zip file');
643
760
  }
644
761
  });
645
762
  });
763
+ // Endpoint to download the matterbridge plugin directory
646
764
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
647
765
  this.log.debug('The frontend sent /api/download-pluginstorage');
648
766
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
649
767
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
768
+ /* istanbul ignore if */
650
769
  if (error) {
651
770
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
652
771
  res.status(500).send('Error downloading the matterbridge plugin storage file');
653
772
  }
654
773
  });
655
774
  });
775
+ // Endpoint to download the matterbridge plugin config files
656
776
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
657
777
  this.log.debug('The frontend sent /api/download-pluginconfig');
658
778
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
659
779
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
780
+ /* istanbul ignore if */
660
781
  if (error) {
661
782
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
662
783
  res.status(500).send('Error downloading the matterbridge plugin config file');
663
784
  }
664
785
  });
665
786
  });
787
+ // Endpoint to download the matterbridge backup (created with the backup command)
666
788
  this.expressApp.get('/api/download-backup', async (req, res) => {
667
789
  this.log.debug('The frontend sent /api/download-backup');
668
790
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
791
+ /* istanbul ignore if */
669
792
  if (error) {
670
793
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
671
794
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
672
795
  }
673
796
  });
674
797
  });
798
+ // Endpoint to upload a package
675
799
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
676
800
  const { filename } = req.body;
677
801
  const file = req.file;
802
+ /* istanbul ignore if */
678
803
  if (!file || !filename) {
679
804
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
680
805
  res.status(400).send('Invalid request: file and filename are required');
681
806
  return;
682
807
  }
683
808
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
809
+ // Define the path where the plugin file will be saved
684
810
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
685
811
  try {
812
+ // Move the uploaded file to the specified path
686
813
  const fs = await import('node:fs');
687
814
  await fs.promises.rename(file.path, filePath);
688
815
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
816
+ // Install the plugin package
689
817
  if (filename.endsWith('.tgz')) {
690
818
  const { spawnCommand } = await import('./utils/spawn.js');
691
819
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -705,6 +833,7 @@ export class Frontend extends EventEmitter {
705
833
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
706
834
  }
707
835
  });
836
+ // Fallback for routing (must be the last route)
708
837
  this.expressApp.use((req, res) => {
709
838
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
710
839
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -715,13 +844,16 @@ export class Frontend extends EventEmitter {
715
844
  async stop() {
716
845
  this.log.debug('Stopping the frontend...');
717
846
  const ws = await import('ws');
847
+ // Remove listeners from the express app
718
848
  if (this.expressApp) {
719
849
  this.expressApp.removeAllListeners();
720
850
  this.expressApp = undefined;
721
851
  this.log.debug('Frontend app closed successfully');
722
852
  }
853
+ // Close the WebSocket server
723
854
  if (this.webSocketServer) {
724
855
  this.log.debug('Closing WebSocket server...');
856
+ // Close all active connections
725
857
  this.webSocketServer.clients.forEach((client) => {
726
858
  if (client.readyState === ws.WebSocket.OPEN) {
727
859
  client.close();
@@ -730,6 +862,7 @@ export class Frontend extends EventEmitter {
730
862
  await withTimeout(new Promise((resolve) => {
731
863
  this.webSocketServer?.close((error) => {
732
864
  if (error) {
865
+ // istanbul ignore next
733
866
  this.log.error(`Error closing WebSocket server: ${error}`);
734
867
  }
735
868
  else {
@@ -742,8 +875,27 @@ export class Frontend extends EventEmitter {
742
875
  this.webSocketServer.removeAllListeners();
743
876
  this.webSocketServer = undefined;
744
877
  }
878
+ // Close the http server
745
879
  if (this.httpServer) {
746
880
  this.log.debug('Closing http server...');
881
+ /*
882
+ await withTimeout(
883
+ new Promise<void>((resolve) => {
884
+ this.httpServer?.close((error) => {
885
+ if (error) {
886
+ // istanbul ignore next
887
+ this.log.error(`Error closing http server: ${error}`);
888
+ } else {
889
+ this.log.debug('Http server closed successfully');
890
+ this.emit('server_stopped');
891
+ }
892
+ resolve();
893
+ });
894
+ }),
895
+ 5000,
896
+ false,
897
+ );
898
+ */
747
899
  this.httpServer.close();
748
900
  this.log.debug('Http server closed successfully');
749
901
  this.listening = false;
@@ -752,8 +904,27 @@ export class Frontend extends EventEmitter {
752
904
  this.httpServer = undefined;
753
905
  this.log.debug('Frontend http server closed successfully');
754
906
  }
907
+ // Close the https server
755
908
  if (this.httpsServer) {
756
909
  this.log.debug('Closing https server...');
910
+ /*
911
+ await withTimeout(
912
+ new Promise<void>((resolve) => {
913
+ this.httpsServer?.close((error) => {
914
+ if (error) {
915
+ // istanbul ignore next
916
+ this.log.error(`Error closing https server: ${error}`);
917
+ } else {
918
+ this.log.debug('Https server closed successfully');
919
+ this.emit('server_stopped');
920
+ }
921
+ resolve();
922
+ });
923
+ }),
924
+ 5000,
925
+ false,
926
+ );
927
+ */
757
928
  this.httpsServer.close();
758
929
  this.log.debug('Https server closed successfully');
759
930
  this.listening = false;
@@ -764,7 +935,13 @@ export class Frontend extends EventEmitter {
764
935
  }
765
936
  this.log.debug('Frontend stopped successfully');
766
937
  }
938
+ /**
939
+ * Retrieves the api settings data.
940
+ *
941
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
942
+ */
767
943
  async getApiSettings() {
944
+ // Update the variable system information properties
768
945
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
769
946
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
770
947
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -774,6 +951,7 @@ export class Frontend extends EventEmitter {
774
951
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
775
952
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
776
953
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
954
+ // Create the matterbridge information
777
955
  const info = {
778
956
  homeDirectory: this.matterbridge.homeDirectory,
779
957
  rootDirectory: this.matterbridge.rootDirectory,
@@ -809,9 +987,15 @@ export class Frontend extends EventEmitter {
809
987
  };
810
988
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
811
989
  }
990
+ /**
991
+ * Retrieves the reachable attribute.
992
+ *
993
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
994
+ * @returns {boolean} The reachable attribute.
995
+ */
812
996
  getReachability(device) {
813
997
  if (this.matterbridge.hasCleanupStarted)
814
- return false;
998
+ return false; // Skip if cleanup has started
815
999
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
816
1000
  return false;
817
1001
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -822,9 +1006,15 @@ export class Frontend extends EventEmitter {
822
1006
  return true;
823
1007
  return false;
824
1008
  }
1009
+ /**
1010
+ * Retrieves the power source attribute.
1011
+ *
1012
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1013
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1014
+ */
825
1015
  getPowerSource(endpoint) {
826
1016
  if (this.matterbridge.hasCleanupStarted)
827
- return;
1017
+ return; // Skip if cleanup has started
828
1018
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
829
1019
  return undefined;
830
1020
  const powerSource = (device) => {
@@ -839,16 +1029,25 @@ export class Frontend extends EventEmitter {
839
1029
  }
840
1030
  return;
841
1031
  };
1032
+ // Root endpoint
842
1033
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
843
1034
  return powerSource(endpoint);
1035
+ // Child endpoints
844
1036
  for (const child of endpoint.getChildEndpoints()) {
845
1037
  if (child.hasClusterServer(PowerSource.Cluster.id))
846
1038
  return powerSource(child);
847
1039
  }
848
1040
  }
1041
+ /**
1042
+ * Retrieves the cluster text description from a given device.
1043
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1044
+ *
1045
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1046
+ * @returns {string} The attributes description of the cluster servers in the device.
1047
+ */
849
1048
  getClusterTextFromDevice(device) {
850
1049
  if (this.matterbridge.hasCleanupStarted)
851
- return '';
1050
+ return ''; // Skip if cleanup has started
852
1051
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
853
1052
  return '';
854
1053
  const getUserLabel = (device) => {
@@ -858,6 +1057,7 @@ export class Frontend extends EventEmitter {
858
1057
  if (composed)
859
1058
  return 'Composed: ' + composed.value;
860
1059
  }
1060
+ // istanbul ignore next cause is not reachable
861
1061
  return '';
862
1062
  };
863
1063
  const getFixedLabel = (device) => {
@@ -867,11 +1067,13 @@ export class Frontend extends EventEmitter {
867
1067
  if (composed)
868
1068
  return 'Composed: ' + composed.value;
869
1069
  }
1070
+ // istanbul ignore next cause is not reacheable
870
1071
  return '';
871
1072
  };
872
1073
  let attributes = '';
873
1074
  let supportedModes = [];
874
1075
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1076
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
875
1077
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
876
1078
  return;
877
1079
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -961,11 +1163,17 @@ export class Frontend extends EventEmitter {
961
1163
  if (clusterName === 'userLabel' && attributeName === 'labelList')
962
1164
  attributes += `${getUserLabel(device)} `;
963
1165
  });
1166
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
964
1167
  return attributes.trimStart().trimEnd();
965
1168
  }
1169
+ /**
1170
+ * Retrieves the registered plugins sanitized for res.json().
1171
+ *
1172
+ * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1173
+ */
966
1174
  getPlugins() {
967
1175
  if (this.matterbridge.hasCleanupStarted)
968
- return [];
1176
+ return []; // Skip if cleanup has started
969
1177
  const plugins = [];
970
1178
  for (const plugin of this.matterbridge.plugins.array()) {
971
1179
  plugins.push({
@@ -993,18 +1201,27 @@ export class Frontend extends EventEmitter {
993
1201
  schemaJson: plugin.schemaJson,
994
1202
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
995
1203
  hasBlackList: plugin.configJson?.blackList !== undefined,
1204
+ // Childbridge mode specific data
996
1205
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
997
1206
  });
998
1207
  }
999
1208
  return plugins;
1000
1209
  }
1210
+ /**
1211
+ * Retrieves the devices from Matterbridge.
1212
+ *
1213
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1214
+ * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1215
+ */
1001
1216
  getDevices(pluginName) {
1002
1217
  if (this.matterbridge.hasCleanupStarted)
1003
- return [];
1218
+ return []; // Skip if cleanup has started
1004
1219
  const devices = [];
1005
1220
  for (const device of this.matterbridge.devices.array()) {
1221
+ // Filter by pluginName if provided
1006
1222
  if (pluginName && pluginName !== device.plugin)
1007
1223
  continue;
1224
+ // Check if the device has the required properties
1008
1225
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1009
1226
  continue;
1010
1227
  devices.push({
@@ -1024,24 +1241,39 @@ export class Frontend extends EventEmitter {
1024
1241
  }
1025
1242
  return devices;
1026
1243
  }
1244
+ /**
1245
+ * Retrieves the clusters from a given plugin and endpoint number.
1246
+ *
1247
+ * Response for /api/clusters
1248
+ *
1249
+ * @param {string} pluginName - The name of the plugin.
1250
+ * @param {number} endpointNumber - The endpoint number.
1251
+ * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1252
+ */
1027
1253
  getClusters(pluginName, endpointNumber) {
1028
1254
  if (this.matterbridge.hasCleanupStarted)
1029
- return;
1255
+ return; // Skip if cleanup has started
1030
1256
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1031
1257
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1032
1258
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1033
1259
  return;
1034
1260
  }
1261
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1262
+ // Get the device types from the main endpoint
1035
1263
  const deviceTypes = [];
1036
1264
  const clusters = [];
1037
1265
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1038
1266
  deviceTypes.push(d.deviceType);
1039
1267
  });
1268
+ // Get the clusters from the main endpoint
1040
1269
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1041
1270
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1042
1271
  return;
1043
1272
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1044
1273
  return;
1274
+ // console.log(
1275
+ // `${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}`,
1276
+ // );
1045
1277
  clusters.push({
1046
1278
  endpoint: endpoint.number.toString(),
1047
1279
  number: endpoint.number,
@@ -1055,12 +1287,19 @@ export class Frontend extends EventEmitter {
1055
1287
  attributeLocalValue: attributeValue,
1056
1288
  });
1057
1289
  });
1290
+ // Get the child endpoints
1058
1291
  const childEndpoints = endpoint.getChildEndpoints();
1292
+ // if (childEndpoints.length === 0) {
1293
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1294
+ // }
1059
1295
  childEndpoints.forEach((childEndpoint) => {
1296
+ // istanbul ignore if cause is not reachable: should never happen but ...
1060
1297
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1061
1298
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1062
1299
  return;
1063
1300
  }
1301
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1302
+ // Get the device types of the child endpoint
1064
1303
  const deviceTypes = [];
1065
1304
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1066
1305
  deviceTypes.push(d.deviceType);
@@ -1070,6 +1309,9 @@ export class Frontend extends EventEmitter {
1070
1309
  return;
1071
1310
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1072
1311
  return;
1312
+ // console.log(
1313
+ // `${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}`,
1314
+ // );
1073
1315
  clusters.push({
1074
1316
  endpoint: childEndpoint.number.toString(),
1075
1317
  number: childEndpoint.number,
@@ -1089,6 +1331,7 @@ export class Frontend extends EventEmitter {
1089
1331
  async generateDiagnostic() {
1090
1332
  this.log.debug('Generating diagnostic...');
1091
1333
  const serverNodes = [];
1334
+ // istanbul ignore else
1092
1335
  if (this.matterbridge.bridgeMode === 'bridge') {
1093
1336
  if (this.matterbridge.serverNode)
1094
1337
  serverNodes.push(this.matterbridge.serverNode);
@@ -1099,6 +1342,7 @@ export class Frontend extends EventEmitter {
1099
1342
  serverNodes.push(plugin.serverNode);
1100
1343
  }
1101
1344
  }
1345
+ // istanbul ignore next
1102
1346
  for (const device of this.matterbridge.devices.array()) {
1103
1347
  if (device.serverNode)
1104
1348
  serverNodes.push(device.serverNode);
@@ -1122,8 +1366,15 @@ export class Frontend extends EventEmitter {
1122
1366
  values: [...serverNodes],
1123
1367
  })));
1124
1368
  delete Logger.destinations.diagnostic;
1125
- await wait(500);
1369
+ await wait(500); // Wait for the log to be written
1126
1370
  }
1371
+ /**
1372
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1373
+ *
1374
+ * @param {WebSocket} client - The websocket client that sent the message.
1375
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1376
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1377
+ */
1127
1378
  async wsMessageHandler(client, message) {
1128
1379
  let data;
1129
1380
  const sendResponse = (data) => {
@@ -1143,7 +1394,7 @@ export class Frontend extends EventEmitter {
1143
1394
  };
1144
1395
  try {
1145
1396
  data = JSON.parse(message.toString());
1146
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1397
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1147
1398
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1148
1399
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1149
1400
  return;
@@ -1200,7 +1451,22 @@ export class Frontend extends EventEmitter {
1200
1451
  }
1201
1452
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1202
1453
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1203
- data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1454
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
1455
+ /*
1456
+ const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
1457
+ if (plugin) {
1458
+ this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1459
+ await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
1460
+ this.wssSendRestartRequired();
1461
+ this.wssSendRefreshRequired('plugins');
1462
+ this.wssSendRefreshRequired('devices');
1463
+ this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1464
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1465
+ } else {
1466
+ this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1467
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1468
+ }
1469
+ */
1204
1470
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1205
1471
  if (plugin) {
1206
1472
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1214,6 +1480,7 @@ export class Frontend extends EventEmitter {
1214
1480
  return;
1215
1481
  })
1216
1482
  .catch((_error) => {
1483
+ //
1217
1484
  });
1218
1485
  }
1219
1486
  else {
@@ -1228,6 +1495,10 @@ export class Frontend extends EventEmitter {
1228
1495
  }
1229
1496
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1230
1497
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1498
+ /*
1499
+ await this.server.fetch({ type: 'plugins_shutdown', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName, reason: 'The plugin has been removed.', removeAllDevices: true } }, 5000);
1500
+ await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
1501
+ */
1231
1502
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1232
1503
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1233
1504
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1263,6 +1534,7 @@ export class Frontend extends EventEmitter {
1263
1534
  return;
1264
1535
  })
1265
1536
  .catch((_error) => {
1537
+ //
1266
1538
  });
1267
1539
  }
1268
1540
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1288,6 +1560,7 @@ export class Frontend extends EventEmitter {
1288
1560
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1289
1561
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1290
1562
  if (plugin.serverNode) {
1563
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1291
1564
  await this.matterbridge.stopServerNode(plugin.serverNode);
1292
1565
  plugin.serverNode = undefined;
1293
1566
  }
@@ -1297,18 +1570,20 @@ export class Frontend extends EventEmitter {
1297
1570
  this.matterbridge.devices.remove(device);
1298
1571
  }
1299
1572
  }
1573
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1300
1574
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1301
1575
  await this.matterbridge.createDynamicPlugin(plugin);
1302
1576
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1303
- plugin.restartRequired = false;
1577
+ plugin.restartRequired = false; // Reset plugin restartRequired
1304
1578
  let needRestart = 0;
1305
1579
  for (const plugin of this.matterbridge.plugins) {
1306
1580
  if (plugin.restartRequired)
1307
1581
  needRestart++;
1308
1582
  }
1309
1583
  if (needRestart === 0) {
1310
- this.wssSendRestartNotRequired(true);
1584
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1311
1585
  }
1586
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1312
1587
  if (plugin.serverNode)
1313
1588
  await this.matterbridge.startServerNode(plugin.serverNode);
1314
1589
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1453,6 +1728,9 @@ export class Frontend extends EventEmitter {
1453
1728
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
1454
1729
  }
1455
1730
  if (data.params.advertise) {
1731
+ // TODO: matter.js 0.16.0
1732
+ // await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
1733
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1456
1734
  const advertiser = serverNode.env.get(DeviceAdvertiser);
1457
1735
  if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
1458
1736
  await advertiser.advertise(true);
@@ -1578,22 +1856,22 @@ export class Frontend extends EventEmitter {
1578
1856
  if (isValidString(data.params.value, 4)) {
1579
1857
  this.log.debug('Matterbridge logger level:', data.params.value);
1580
1858
  if (data.params.value === 'Debug') {
1581
- await this.matterbridge.setLogLevel("debug");
1859
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1582
1860
  }
1583
1861
  else if (data.params.value === 'Info') {
1584
- await this.matterbridge.setLogLevel("info");
1862
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1585
1863
  }
1586
1864
  else if (data.params.value === 'Notice') {
1587
- await this.matterbridge.setLogLevel("notice");
1865
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1588
1866
  }
1589
1867
  else if (data.params.value === 'Warn') {
1590
- await this.matterbridge.setLogLevel("warn");
1868
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1591
1869
  }
1592
1870
  else if (data.params.value === 'Error') {
1593
- await this.matterbridge.setLogLevel("error");
1871
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1594
1872
  }
1595
1873
  else if (data.params.value === 'Fatal') {
1596
- await this.matterbridge.setLogLevel("fatal");
1874
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1597
1875
  }
1598
1876
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1599
1877
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1604,6 +1882,7 @@ export class Frontend extends EventEmitter {
1604
1882
  this.log.debug('Matterbridge file log:', data.params.value);
1605
1883
  this.matterbridge.fileLogger = data.params.value;
1606
1884
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1885
+ // Create the file logger for matterbridge
1607
1886
  if (data.params.value)
1608
1887
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1609
1888
  else
@@ -1632,11 +1911,12 @@ export class Frontend extends EventEmitter {
1632
1911
  else if (data.params.value === 'Fatal') {
1633
1912
  Logger.level = MatterLogLevel.FATAL;
1634
1913
  }
1635
- let callbackLogLevel = "notice";
1636
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1637
- callbackLogLevel = "info";
1638
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1639
- callbackLogLevel = "debug";
1914
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1915
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1916
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1917
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1918
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1919
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1640
1920
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1641
1921
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1642
1922
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -1688,6 +1968,7 @@ export class Frontend extends EventEmitter {
1688
1968
  }
1689
1969
  break;
1690
1970
  case 'setmatterport':
1971
+ // eslint-disable-next-line no-case-declarations
1691
1972
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1692
1973
  if (isValidNumber(port, 5540, 5600)) {
1693
1974
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1707,6 +1988,7 @@ export class Frontend extends EventEmitter {
1707
1988
  }
1708
1989
  break;
1709
1990
  case 'setmatterdiscriminator':
1991
+ // eslint-disable-next-line no-case-declarations
1710
1992
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1711
1993
  if (isValidNumber(discriminator, 0, 4095)) {
1712
1994
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1726,6 +2008,7 @@ export class Frontend extends EventEmitter {
1726
2008
  }
1727
2009
  break;
1728
2010
  case 'setmatterpasscode':
2011
+ // eslint-disable-next-line no-case-declarations
1729
2012
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1730
2013
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1731
2014
  this.matterbridge.passcode = passcode;
@@ -1771,15 +2054,19 @@ export class Frontend extends EventEmitter {
1771
2054
  return;
1772
2055
  }
1773
2056
  const config = plugin.configJson;
2057
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1774
2058
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2059
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1775
2060
  if (select === 'serial')
1776
2061
  this.log.info(`Selected device serial ${data.params.serial}`);
1777
2062
  if (select === 'name')
1778
2063
  this.log.info(`Selected device name ${data.params.name}`);
1779
2064
  if (config && select && (select === 'serial' || select === 'name')) {
2065
+ // Remove postfix from the serial if it exists
1780
2066
  if (config.postfix) {
1781
2067
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1782
2068
  }
2069
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1783
2070
  if (isValidArray(config.whiteList, 1)) {
1784
2071
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1785
2072
  config.whiteList.push(data.params.serial);
@@ -1788,6 +2075,7 @@ export class Frontend extends EventEmitter {
1788
2075
  config.whiteList.push(data.params.name);
1789
2076
  }
1790
2077
  }
2078
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1791
2079
  if (isValidArray(config.blackList, 1)) {
1792
2080
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1793
2081
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1815,7 +2103,9 @@ export class Frontend extends EventEmitter {
1815
2103
  return;
1816
2104
  }
1817
2105
  const config = plugin.configJson;
2106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1818
2107
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2108
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1819
2109
  if (select === 'serial')
1820
2110
  this.log.info(`Unselected device serial ${data.params.serial}`);
1821
2111
  if (select === 'name')
@@ -1824,6 +2114,7 @@ export class Frontend extends EventEmitter {
1824
2114
  if (config.postfix) {
1825
2115
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1826
2116
  }
2117
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1827
2118
  if (isValidArray(config.whiteList, 1)) {
1828
2119
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1829
2120
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1832,6 +2123,7 @@ export class Frontend extends EventEmitter {
1832
2123
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1833
2124
  }
1834
2125
  }
2126
+ // Add the serial to the blackList
1835
2127
  if (isValidArray(config.blackList)) {
1836
2128
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1837
2129
  config.blackList.push(data.params.serial);
@@ -1854,6 +2146,7 @@ export class Frontend extends EventEmitter {
1854
2146
  }
1855
2147
  }
1856
2148
  else {
2149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1857
2150
  const localData = data;
1858
2151
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1859
2152
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1863,23 +2156,46 @@ export class Frontend extends EventEmitter {
1863
2156
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1864
2157
  }
1865
2158
  }
2159
+ /**
2160
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2161
+ *
2162
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2163
+ * @param {string} time - The time string of the message
2164
+ * @param {string} name - The logger name of the message
2165
+ * @param {string} message - The content of the message.
2166
+ *
2167
+ * @remarks
2168
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2169
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2170
+ * The function sends the message to all connected clients.
2171
+ */
1866
2172
  wssSendLogMessage(level, time, name, message) {
1867
2173
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1868
2174
  return;
1869
2175
  if (!level || !time || !name || !message)
1870
2176
  return;
2177
+ // Remove ANSI escape codes from the message
2178
+ // eslint-disable-next-line no-control-regex
1871
2179
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2180
+ // Remove leading asterisks from the message
1872
2181
  message = message.replace(/^\*+/, '');
2182
+ // Replace all occurrences of \t and \n
1873
2183
  message = message.replace(/[\t\n]/g, '');
2184
+ // Remove non-printable characters
2185
+ // eslint-disable-next-line no-control-regex
1874
2186
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2187
+ // Replace all occurrences of \" with "
1875
2188
  message = message.replace(/\\"/g, '"');
2189
+ // Define the maximum allowed length for continuous characters without a space
1876
2190
  const maxContinuousLength = 100;
1877
2191
  const keepStartLength = 20;
1878
2192
  const keepEndLength = 20;
2193
+ // Split the message into words
1879
2194
  if (level !== 'spawn') {
1880
2195
  message = message
1881
2196
  .split(' ')
1882
2197
  .map((word) => {
2198
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1883
2199
  if (word.length > maxContinuousLength) {
1884
2200
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1885
2201
  }
@@ -1887,14 +2203,34 @@ export class Frontend extends EventEmitter {
1887
2203
  })
1888
2204
  .join(' ');
1889
2205
  }
2206
+ // Send the message to all connected clients
1890
2207
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
1891
2208
  }
2209
+ /**
2210
+ * Sends a need to refresh WebSocket message to all connected clients.
2211
+ *
2212
+ * @param {string} changed - The changed value.
2213
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2214
+ * possible values for changed:
2215
+ * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2216
+ * - 'plugins'
2217
+ * - 'devices'
2218
+ * - 'matter' with param 'matter' (QRDiv component)
2219
+ * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2220
+ */
1892
2221
  wssSendRefreshRequired(changed, params) {
1893
2222
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1894
2223
  return;
1895
2224
  this.log.debug('Sending a refresh required message to all connected clients');
2225
+ // Send the message to all connected clients
1896
2226
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
1897
2227
  }
2228
+ /**
2229
+ * Sends a need to restart WebSocket message to all connected clients.
2230
+ *
2231
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2232
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2233
+ */
1898
2234
  wssSendRestartRequired(snackbar = true, fixed = false) {
1899
2235
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1900
2236
  return;
@@ -1903,8 +2239,14 @@ export class Frontend extends EventEmitter {
1903
2239
  this.matterbridge.fixedRestartRequired = fixed;
1904
2240
  if (snackbar === true)
1905
2241
  this.wssSendSnackbarMessage(`Restart required`, 0);
2242
+ // Send the message to all connected clients
1906
2243
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1907
2244
  }
2245
+ /**
2246
+ * Sends a no need to restart WebSocket message to all connected clients.
2247
+ *
2248
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2249
+ */
1908
2250
  wssSendRestartNotRequired(snackbar = true) {
1909
2251
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1910
2252
  return;
@@ -1912,57 +2254,133 @@ export class Frontend extends EventEmitter {
1912
2254
  this.matterbridge.restartRequired = false;
1913
2255
  if (snackbar === true)
1914
2256
  this.wssSendCloseSnackbarMessage(`Restart required`);
2257
+ // Send the message to all connected clients
1915
2258
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1916
2259
  }
2260
+ /**
2261
+ * Sends a need to update WebSocket message to all connected clients.
2262
+ *
2263
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2264
+ */
1917
2265
  wssSendUpdateRequired(devVersion = false) {
1918
2266
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1919
2267
  return;
1920
2268
  this.log.debug('Sending an update required message to all connected clients');
1921
2269
  this.matterbridge.updateRequired = true;
2270
+ // Send the message to all connected clients
1922
2271
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1923
2272
  }
2273
+ /**
2274
+ * Sends a cpu update message to all connected clients.
2275
+ *
2276
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2277
+ * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2278
+ */
1924
2279
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
1925
2280
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1926
2281
  return;
1927
2282
  if (hasParameter('debug'))
1928
2283
  this.log.debug('Sending a cpu update message to all connected clients');
2284
+ // Send the message to all connected clients
1929
2285
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 } });
1930
2286
  }
2287
+ /**
2288
+ * Sends a memory update message to all connected clients.
2289
+ *
2290
+ * @param {string} totalMemory - The total memory in bytes.
2291
+ * @param {string} freeMemory - The free memory in bytes.
2292
+ * @param {string} rss - The resident set size in bytes.
2293
+ * @param {string} heapTotal - The total heap memory in bytes.
2294
+ * @param {string} heapUsed - The used heap memory in bytes.
2295
+ * @param {string} external - The external memory in bytes.
2296
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2297
+ */
1931
2298
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1932
2299
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1933
2300
  return;
1934
2301
  if (hasParameter('debug'))
1935
2302
  this.log.debug('Sending a memory update message to all connected clients');
2303
+ // Send the message to all connected clients
1936
2304
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1937
2305
  }
2306
+ /**
2307
+ * Sends an uptime update message to all connected clients.
2308
+ *
2309
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2310
+ * @param {string} processUptime - The process uptime in a human-readable format.
2311
+ */
1938
2312
  wssSendUptimeUpdate(systemUptime, processUptime) {
1939
2313
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1940
2314
  return;
1941
2315
  if (hasParameter('debug'))
1942
2316
  this.log.debug('Sending a uptime update message to all connected clients');
2317
+ // Send the message to all connected clients
1943
2318
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
1944
2319
  }
2320
+ /**
2321
+ * Sends an open snackbar message to all connected clients.
2322
+ *
2323
+ * @param {string} message - The message to send.
2324
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2325
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2326
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2327
+ *
2328
+ * @remarks
2329
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2330
+ */
1945
2331
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1946
2332
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1947
2333
  return;
1948
2334
  this.log.debug('Sending a snackbar message to all connected clients');
2335
+ // Send the message to all connected clients
1949
2336
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
1950
2337
  }
2338
+ /**
2339
+ * Sends a close snackbar message to all connected clients.
2340
+ * It will close the snackbar message with the same message and timeout = 0.
2341
+ *
2342
+ * @param {string} message - The message to send.
2343
+ */
1951
2344
  wssSendCloseSnackbarMessage(message) {
1952
2345
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1953
2346
  return;
1954
2347
  this.log.debug('Sending a close snackbar message to all connected clients');
2348
+ // Send the message to all connected clients
1955
2349
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1956
2350
  }
2351
+ /**
2352
+ * Sends an attribute update message to all connected WebSocket clients.
2353
+ *
2354
+ * @param {string | undefined} plugin - The name of the plugin.
2355
+ * @param {string | undefined} serialNumber - The serial number of the device.
2356
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2357
+ * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2358
+ * @param {string} id - The endpoint id where the attribute belongs.
2359
+ * @param {string} cluster - The cluster name where the attribute belongs.
2360
+ * @param {string} attribute - The name of the attribute that changed.
2361
+ * @param {number | string | boolean} value - The new value of the attribute.
2362
+ *
2363
+ * @remarks
2364
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2365
+ * with the updated attribute information.
2366
+ */
1957
2367
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1958
2368
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1959
2369
  return;
1960
2370
  this.log.debug('Sending an attribute update message to all connected clients');
2371
+ // Send the message to all connected clients
1961
2372
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1962
2373
  }
2374
+ /**
2375
+ * Sends a message to all connected clients.
2376
+ * This is an helper function to send a broadcast message to all connected clients.
2377
+ *
2378
+ * @param {WsMessageBroadcast} msg - The message to send.
2379
+ */
1963
2380
  wssBroadcastMessage(msg) {
1964
2381
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1965
2382
  return;
2383
+ // Send the message to all connected clients
1966
2384
  const stringifiedMsg = JSON.stringify(msg);
1967
2385
  if (msg.method !== 'log')
1968
2386
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -1973,3 +2391,4 @@ export class Frontend extends EventEmitter {
1973
2391
  });
1974
2392
  }
1975
2393
  }
2394
+ //# sourceMappingURL=frontend.js.map