matterbridge 3.3.6 → 3.3.7-dev-20251104-7c779b9

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 (306) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/broadcastServer.js +1 -92
  3. package/dist/broadcastServerTypes.js +0 -24
  4. package/dist/cli.js +1 -97
  5. package/dist/cliEmitter.js +0 -37
  6. package/dist/cliHistory.js +0 -38
  7. package/dist/clusters/export.js +0 -2
  8. package/dist/defaultConfigSchema.js +0 -24
  9. package/dist/deviceManager.js +1 -124
  10. package/dist/devices/airConditioner.js +0 -57
  11. package/dist/devices/batteryStorage.js +1 -48
  12. package/dist/devices/cooktop.js +0 -55
  13. package/dist/devices/dishwasher.js +0 -57
  14. package/dist/devices/evse.js +10 -74
  15. package/dist/devices/export.js +0 -5
  16. package/dist/devices/extractorHood.js +0 -42
  17. package/dist/devices/heatPump.js +2 -50
  18. package/dist/devices/laundryDryer.js +3 -62
  19. package/dist/devices/laundryWasher.js +4 -70
  20. package/dist/devices/microwaveOven.js +5 -88
  21. package/dist/devices/oven.js +0 -85
  22. package/dist/devices/refrigerator.js +0 -102
  23. package/dist/devices/roboticVacuumCleaner.js +9 -100
  24. package/dist/devices/solarPower.js +0 -38
  25. package/dist/devices/speaker.js +0 -84
  26. package/dist/devices/temperatureControl.js +3 -24
  27. package/dist/devices/waterHeater.js +2 -82
  28. package/dist/dgram/coap.js +13 -126
  29. package/dist/dgram/dgram.js +2 -114
  30. package/dist/dgram/mb_coap.js +3 -41
  31. package/dist/dgram/mb_mdns.js +15 -80
  32. package/dist/dgram/mdns.js +137 -299
  33. package/dist/dgram/multicast.js +1 -62
  34. package/dist/dgram/unicast.js +0 -54
  35. package/dist/frontend.js +55 -451
  36. package/dist/frontendTypes.js +0 -45
  37. package/dist/helpers.js +0 -53
  38. package/dist/index.js +0 -25
  39. package/dist/logger/export.js +0 -1
  40. package/dist/matter/behaviors.js +0 -2
  41. package/dist/matter/clusters.js +0 -2
  42. package/dist/matter/devices.js +0 -2
  43. package/dist/matter/endpoints.js +0 -2
  44. package/dist/matter/export.js +0 -3
  45. package/dist/matter/types.js +0 -3
  46. package/dist/matterbridge.js +66 -841
  47. package/dist/matterbridgeAccessoryPlatform.js +0 -37
  48. package/dist/matterbridgeBehaviors.js +5 -68
  49. package/dist/matterbridgeDeviceTypes.js +17 -638
  50. package/dist/matterbridgeDynamicPlatform.js +0 -37
  51. package/dist/matterbridgeEndpoint.js +52 -1408
  52. package/dist/matterbridgeEndpointHelpers.js +19 -464
  53. package/dist/matterbridgePlatform.js +1 -341
  54. package/dist/matterbridgeTypes.js +0 -26
  55. package/dist/pluginManager.js +3 -319
  56. package/dist/shelly.js +7 -168
  57. package/dist/storage/export.js +0 -1
  58. package/dist/update.js +0 -69
  59. package/dist/utils/colorUtils.js +2 -97
  60. package/dist/utils/commandLine.js +0 -60
  61. package/dist/utils/copyDirectory.js +1 -38
  62. package/dist/utils/createDirectory.js +0 -33
  63. package/dist/utils/createZip.js +2 -47
  64. package/dist/utils/deepCopy.js +0 -39
  65. package/dist/utils/deepEqual.js +1 -72
  66. package/dist/utils/error.js +0 -41
  67. package/dist/utils/export.js +0 -1
  68. package/dist/utils/format.js +0 -49
  69. package/dist/utils/hex.js +0 -124
  70. package/dist/utils/inspector.js +1 -69
  71. package/dist/utils/isvalid.js +0 -101
  72. package/dist/utils/jestHelpers.js +3 -153
  73. package/dist/utils/network.js +5 -96
  74. package/dist/utils/spawn.js +0 -71
  75. package/dist/utils/tracker.js +1 -64
  76. package/dist/utils/wait.js +8 -60
  77. package/frontend/build/assets/index.css +1 -1
  78. package/frontend/build/assets/index.js +4 -4
  79. package/frontend/package.json +1 -1
  80. package/npm-shrinkwrap.json +2 -2
  81. package/package.json +1 -2
  82. package/dist/broadcastServer.d.ts +0 -112
  83. package/dist/broadcastServer.d.ts.map +0 -1
  84. package/dist/broadcastServer.js.map +0 -1
  85. package/dist/broadcastServerTypes.d.ts +0 -803
  86. package/dist/broadcastServerTypes.d.ts.map +0 -1
  87. package/dist/broadcastServerTypes.js.map +0 -1
  88. package/dist/cli.d.ts +0 -30
  89. package/dist/cli.d.ts.map +0 -1
  90. package/dist/cli.js.map +0 -1
  91. package/dist/cliEmitter.d.ts +0 -50
  92. package/dist/cliEmitter.d.ts.map +0 -1
  93. package/dist/cliEmitter.js.map +0 -1
  94. package/dist/cliHistory.d.ts +0 -48
  95. package/dist/cliHistory.d.ts.map +0 -1
  96. package/dist/cliHistory.js.map +0 -1
  97. package/dist/clusters/export.d.ts +0 -2
  98. package/dist/clusters/export.d.ts.map +0 -1
  99. package/dist/clusters/export.js.map +0 -1
  100. package/dist/defaultConfigSchema.d.ts +0 -28
  101. package/dist/defaultConfigSchema.d.ts.map +0 -1
  102. package/dist/defaultConfigSchema.js.map +0 -1
  103. package/dist/deviceManager.d.ts +0 -117
  104. package/dist/deviceManager.d.ts.map +0 -1
  105. package/dist/deviceManager.js.map +0 -1
  106. package/dist/devices/airConditioner.d.ts +0 -98
  107. package/dist/devices/airConditioner.d.ts.map +0 -1
  108. package/dist/devices/airConditioner.js.map +0 -1
  109. package/dist/devices/batteryStorage.d.ts +0 -48
  110. package/dist/devices/batteryStorage.d.ts.map +0 -1
  111. package/dist/devices/batteryStorage.js.map +0 -1
  112. package/dist/devices/cooktop.d.ts +0 -60
  113. package/dist/devices/cooktop.d.ts.map +0 -1
  114. package/dist/devices/cooktop.js.map +0 -1
  115. package/dist/devices/dishwasher.d.ts +0 -71
  116. package/dist/devices/dishwasher.d.ts.map +0 -1
  117. package/dist/devices/dishwasher.js.map +0 -1
  118. package/dist/devices/evse.d.ts +0 -76
  119. package/dist/devices/evse.d.ts.map +0 -1
  120. package/dist/devices/evse.js.map +0 -1
  121. package/dist/devices/export.d.ts +0 -17
  122. package/dist/devices/export.d.ts.map +0 -1
  123. package/dist/devices/export.js.map +0 -1
  124. package/dist/devices/extractorHood.d.ts +0 -46
  125. package/dist/devices/extractorHood.d.ts.map +0 -1
  126. package/dist/devices/extractorHood.js.map +0 -1
  127. package/dist/devices/heatPump.d.ts +0 -47
  128. package/dist/devices/heatPump.d.ts.map +0 -1
  129. package/dist/devices/heatPump.js.map +0 -1
  130. package/dist/devices/laundryDryer.d.ts +0 -67
  131. package/dist/devices/laundryDryer.d.ts.map +0 -1
  132. package/dist/devices/laundryDryer.js.map +0 -1
  133. package/dist/devices/laundryWasher.d.ts +0 -81
  134. package/dist/devices/laundryWasher.d.ts.map +0 -1
  135. package/dist/devices/laundryWasher.js.map +0 -1
  136. package/dist/devices/microwaveOven.d.ts +0 -168
  137. package/dist/devices/microwaveOven.d.ts.map +0 -1
  138. package/dist/devices/microwaveOven.js.map +0 -1
  139. package/dist/devices/oven.d.ts +0 -105
  140. package/dist/devices/oven.d.ts.map +0 -1
  141. package/dist/devices/oven.js.map +0 -1
  142. package/dist/devices/refrigerator.d.ts +0 -118
  143. package/dist/devices/refrigerator.d.ts.map +0 -1
  144. package/dist/devices/refrigerator.js.map +0 -1
  145. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  146. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  147. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  148. package/dist/devices/solarPower.d.ts +0 -40
  149. package/dist/devices/solarPower.d.ts.map +0 -1
  150. package/dist/devices/solarPower.js.map +0 -1
  151. package/dist/devices/speaker.d.ts +0 -87
  152. package/dist/devices/speaker.d.ts.map +0 -1
  153. package/dist/devices/speaker.js.map +0 -1
  154. package/dist/devices/temperatureControl.d.ts +0 -166
  155. package/dist/devices/temperatureControl.d.ts.map +0 -1
  156. package/dist/devices/temperatureControl.js.map +0 -1
  157. package/dist/devices/waterHeater.d.ts +0 -111
  158. package/dist/devices/waterHeater.d.ts.map +0 -1
  159. package/dist/devices/waterHeater.js.map +0 -1
  160. package/dist/dgram/coap.d.ts +0 -205
  161. package/dist/dgram/coap.d.ts.map +0 -1
  162. package/dist/dgram/coap.js.map +0 -1
  163. package/dist/dgram/dgram.d.ts +0 -141
  164. package/dist/dgram/dgram.d.ts.map +0 -1
  165. package/dist/dgram/dgram.js.map +0 -1
  166. package/dist/dgram/mb_coap.d.ts +0 -24
  167. package/dist/dgram/mb_coap.d.ts.map +0 -1
  168. package/dist/dgram/mb_coap.js.map +0 -1
  169. package/dist/dgram/mb_mdns.d.ts +0 -24
  170. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  171. package/dist/dgram/mb_mdns.js.map +0 -1
  172. package/dist/dgram/mdns.d.ts +0 -290
  173. package/dist/dgram/mdns.d.ts.map +0 -1
  174. package/dist/dgram/mdns.js.map +0 -1
  175. package/dist/dgram/multicast.d.ts +0 -67
  176. package/dist/dgram/multicast.d.ts.map +0 -1
  177. package/dist/dgram/multicast.js.map +0 -1
  178. package/dist/dgram/unicast.d.ts +0 -56
  179. package/dist/dgram/unicast.d.ts.map +0 -1
  180. package/dist/dgram/unicast.js.map +0 -1
  181. package/dist/frontend.d.ts +0 -236
  182. package/dist/frontend.d.ts.map +0 -1
  183. package/dist/frontend.js.map +0 -1
  184. package/dist/frontendTypes.d.ts +0 -529
  185. package/dist/frontendTypes.d.ts.map +0 -1
  186. package/dist/frontendTypes.js.map +0 -1
  187. package/dist/helpers.d.ts +0 -48
  188. package/dist/helpers.d.ts.map +0 -1
  189. package/dist/helpers.js.map +0 -1
  190. package/dist/index.d.ts +0 -33
  191. package/dist/index.d.ts.map +0 -1
  192. package/dist/index.js.map +0 -1
  193. package/dist/logger/export.d.ts +0 -2
  194. package/dist/logger/export.d.ts.map +0 -1
  195. package/dist/logger/export.js.map +0 -1
  196. package/dist/matter/behaviors.d.ts +0 -2
  197. package/dist/matter/behaviors.d.ts.map +0 -1
  198. package/dist/matter/behaviors.js.map +0 -1
  199. package/dist/matter/clusters.d.ts +0 -2
  200. package/dist/matter/clusters.d.ts.map +0 -1
  201. package/dist/matter/clusters.js.map +0 -1
  202. package/dist/matter/devices.d.ts +0 -2
  203. package/dist/matter/devices.d.ts.map +0 -1
  204. package/dist/matter/devices.js.map +0 -1
  205. package/dist/matter/endpoints.d.ts +0 -2
  206. package/dist/matter/endpoints.d.ts.map +0 -1
  207. package/dist/matter/endpoints.js.map +0 -1
  208. package/dist/matter/export.d.ts +0 -5
  209. package/dist/matter/export.d.ts.map +0 -1
  210. package/dist/matter/export.js.map +0 -1
  211. package/dist/matter/types.d.ts +0 -3
  212. package/dist/matter/types.d.ts.map +0 -1
  213. package/dist/matter/types.js.map +0 -1
  214. package/dist/matterbridge.d.ts +0 -476
  215. package/dist/matterbridge.d.ts.map +0 -1
  216. package/dist/matterbridge.js.map +0 -1
  217. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  218. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  219. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  220. package/dist/matterbridgeBehaviors.d.ts +0 -2404
  221. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  222. package/dist/matterbridgeBehaviors.js.map +0 -1
  223. package/dist/matterbridgeDeviceTypes.d.ts +0 -770
  224. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  225. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  226. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  227. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  228. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  229. package/dist/matterbridgeEndpoint.d.ts +0 -1556
  230. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  231. package/dist/matterbridgeEndpoint.js.map +0 -1
  232. package/dist/matterbridgeEndpointHelpers.d.ts +0 -758
  233. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  234. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  235. package/dist/matterbridgePlatform.d.ts +0 -402
  236. package/dist/matterbridgePlatform.d.ts.map +0 -1
  237. package/dist/matterbridgePlatform.js.map +0 -1
  238. package/dist/matterbridgeTypes.d.ts +0 -226
  239. package/dist/matterbridgeTypes.d.ts.map +0 -1
  240. package/dist/matterbridgeTypes.js.map +0 -1
  241. package/dist/pluginManager.d.ts +0 -347
  242. package/dist/pluginManager.d.ts.map +0 -1
  243. package/dist/pluginManager.js.map +0 -1
  244. package/dist/shelly.d.ts +0 -174
  245. package/dist/shelly.d.ts.map +0 -1
  246. package/dist/shelly.js.map +0 -1
  247. package/dist/storage/export.d.ts +0 -2
  248. package/dist/storage/export.d.ts.map +0 -1
  249. package/dist/storage/export.js.map +0 -1
  250. package/dist/update.d.ts +0 -75
  251. package/dist/update.d.ts.map +0 -1
  252. package/dist/update.js.map +0 -1
  253. package/dist/utils/colorUtils.d.ts +0 -101
  254. package/dist/utils/colorUtils.d.ts.map +0 -1
  255. package/dist/utils/colorUtils.js.map +0 -1
  256. package/dist/utils/commandLine.d.ts +0 -66
  257. package/dist/utils/commandLine.d.ts.map +0 -1
  258. package/dist/utils/commandLine.js.map +0 -1
  259. package/dist/utils/copyDirectory.d.ts +0 -33
  260. package/dist/utils/copyDirectory.d.ts.map +0 -1
  261. package/dist/utils/copyDirectory.js.map +0 -1
  262. package/dist/utils/createDirectory.d.ts +0 -34
  263. package/dist/utils/createDirectory.d.ts.map +0 -1
  264. package/dist/utils/createDirectory.js.map +0 -1
  265. package/dist/utils/createZip.d.ts +0 -39
  266. package/dist/utils/createZip.d.ts.map +0 -1
  267. package/dist/utils/createZip.js.map +0 -1
  268. package/dist/utils/deepCopy.d.ts +0 -32
  269. package/dist/utils/deepCopy.d.ts.map +0 -1
  270. package/dist/utils/deepCopy.js.map +0 -1
  271. package/dist/utils/deepEqual.d.ts +0 -54
  272. package/dist/utils/deepEqual.d.ts.map +0 -1
  273. package/dist/utils/deepEqual.js.map +0 -1
  274. package/dist/utils/error.d.ts +0 -44
  275. package/dist/utils/error.d.ts.map +0 -1
  276. package/dist/utils/error.js.map +0 -1
  277. package/dist/utils/export.d.ts +0 -13
  278. package/dist/utils/export.d.ts.map +0 -1
  279. package/dist/utils/export.js.map +0 -1
  280. package/dist/utils/format.d.ts +0 -53
  281. package/dist/utils/format.d.ts.map +0 -1
  282. package/dist/utils/format.js.map +0 -1
  283. package/dist/utils/hex.d.ts +0 -89
  284. package/dist/utils/hex.d.ts.map +0 -1
  285. package/dist/utils/hex.js.map +0 -1
  286. package/dist/utils/inspector.d.ts +0 -87
  287. package/dist/utils/inspector.d.ts.map +0 -1
  288. package/dist/utils/inspector.js.map +0 -1
  289. package/dist/utils/isvalid.d.ts +0 -103
  290. package/dist/utils/isvalid.d.ts.map +0 -1
  291. package/dist/utils/isvalid.js.map +0 -1
  292. package/dist/utils/jestHelpers.d.ts +0 -139
  293. package/dist/utils/jestHelpers.d.ts.map +0 -1
  294. package/dist/utils/jestHelpers.js.map +0 -1
  295. package/dist/utils/network.d.ts +0 -101
  296. package/dist/utils/network.d.ts.map +0 -1
  297. package/dist/utils/network.js.map +0 -1
  298. package/dist/utils/spawn.d.ts +0 -35
  299. package/dist/utils/spawn.d.ts.map +0 -1
  300. package/dist/utils/spawn.js.map +0 -1
  301. package/dist/utils/tracker.d.ts +0 -108
  302. package/dist/utils/tracker.d.ts.map +0 -1
  303. package/dist/utils/tracker.js.map +0 -1
  304. package/dist/utils/wait.d.ts +0 -54
  305. package/dist/utils/wait.d.ts.map +0 -1
  306. package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js CHANGED
@@ -1,34 +1,8 @@
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
25
1
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
26
2
  console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
27
- // Node modules
28
3
  import os from 'node:os';
29
4
  import path from 'node:path';
30
5
  import EventEmitter from 'node:events';
31
- // AnsiLogger module
32
6
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
33
7
  import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
34
8
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
@@ -61,7 +35,7 @@ export class Frontend extends EventEmitter {
61
35
  constructor(matterbridge) {
62
36
  super();
63
37
  this.matterbridge = matterbridge;
64
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
38
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
65
39
  this.log.logNameColor = '\x1b[38;5;97m';
66
40
  this.server = new BroadcastServer('frontend', this.log);
67
41
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -156,53 +130,23 @@ export class Frontend extends EventEmitter {
156
130
  this.port = port;
157
131
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
158
132
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
159
- // Initialize multer with the upload directory
160
133
  const multer = await import('multer');
161
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
134
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
162
135
  const upload = multer.default({ dest: uploadDir });
163
- // Create the express app that serves the frontend
164
136
  const express = await import('express');
165
137
  this.expressApp = express.default();
166
- // Inject logging/debug wrapper for route/middleware registration
167
- /*
168
- const methods = ['get', 'post', 'put', 'delete', 'use'];
169
- for (const method of methods) {
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
- const original = (this.expressApp as any)[method].bind(this.expressApp);
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
174
- try {
175
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
176
- return original(path, ...rest);
177
- } catch (err) {
178
- console.error(`[ERROR] Failed to register route: ${path}`);
179
- throw err;
180
- }
181
- };
182
- }
183
- */
184
- // Log all requests to the server for debugging
185
- /*
186
- this.expressApp.use((req, res, next) => {
187
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
188
- next();
189
- });
190
- */
191
- // Serve static files from 'frontend/build' directory
192
- this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
193
- // Create a WebSocket server and attach it to the http or https server
138
+ this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
194
139
  this.log.debug(`Creating WebSocketServer...`);
195
140
  const ws = await import('ws');
196
141
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
197
142
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
198
143
  this.webSocketServer.on('connection', (ws, request) => {
199
144
  const clientIp = request.socket.remoteAddress;
200
- // Set the global logger callback for the WebSocketServer
201
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
202
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
203
- callbackLogLevel = "info" /* LogLevel.INFO */;
204
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
205
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
145
+ let callbackLogLevel = "notice";
146
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
147
+ callbackLogLevel = "info";
148
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
149
+ callbackLogLevel = "debug";
206
150
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
207
151
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
208
152
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -224,25 +168,16 @@ export class Frontend extends EventEmitter {
224
168
  }
225
169
  });
226
170
  ws.on('error', (error) => {
227
- // istanbul ignore next
228
171
  this.log.error(`WebSocket client error: ${error}`);
229
172
  });
230
173
  });
231
174
  this.webSocketServer.on('close', () => {
232
175
  this.log.debug(`WebSocketServer closed`);
233
176
  });
234
- /* With { noServer: true } it never fires
235
- this.webSocketServer.on('listening', () => {
236
- this.log.info(`The WebSocketServer is listening`);
237
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
238
- });
239
- */
240
- // istanbul ignore next
241
177
  this.webSocketServer.on('error', (ws, error) => {
242
178
  this.log.error(`WebSocketServer error: ${error}`);
243
179
  });
244
180
  if (!hasParameter('ssl')) {
245
- // Create an HTTP server and attach the express app
246
181
  const http = await import('node:http');
247
182
  try {
248
183
  this.log.debug(`Creating HTTP server...`);
@@ -253,7 +188,6 @@ export class Frontend extends EventEmitter {
253
188
  this.emit('server_error', error);
254
189
  return;
255
190
  }
256
- // Listen on the specified port
257
191
  if (hasParameter('ingress')) {
258
192
  this.httpServer.listen(this.port, '0.0.0.0', () => {
259
193
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -273,29 +207,23 @@ export class Frontend extends EventEmitter {
273
207
  }
274
208
  this.httpServer.on('upgrade', async (req, socket, head) => {
275
209
  try {
276
- // Only proceed for real WebSocket upgrades
277
- // istanbul ignore next cause is only a safety check
278
210
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
279
211
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
280
212
  return socket.destroy();
281
213
  }
282
- // Build a URL so we can read ?password=...
283
214
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
284
- // Validate WebSocket password
285
215
  const password = url.searchParams.get('password') ?? '';
286
216
  if (password !== this.storedPassword) {
287
217
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
288
218
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
289
219
  return socket.destroy();
290
220
  }
291
- // Complete the WebSocket handshake
292
221
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
293
222
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
294
223
  this.webSocketServer?.emit('connection', ws, req);
295
224
  });
296
225
  }
297
226
  catch (err) {
298
- /* istanbul ignore next: only triggered on unexpected internal error */
299
227
  {
300
228
  inspectError(this.log, 'WebSocket upgrade error:', err);
301
229
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -318,7 +246,6 @@ export class Frontend extends EventEmitter {
318
246
  });
319
247
  }
320
248
  else {
321
- // SSL is enabled, load the certificate and the private key
322
249
  let cert;
323
250
  let key;
324
251
  let ca;
@@ -327,64 +254,61 @@ export class Frontend extends EventEmitter {
327
254
  let passphrase;
328
255
  let httpsServerOptions = {};
329
256
  const fs = await import('node:fs');
330
- if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
331
- // Load the p12 certificate and the passphrase
257
+ if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
332
258
  try {
333
- pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
334
- this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
259
+ pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
260
+ this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
335
261
  }
336
262
  catch (error) {
337
- this.log.error(`Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}: ${error}`);
263
+ this.log.error(`Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}: ${error}`);
338
264
  this.emit('server_error', error);
339
265
  return;
340
266
  }
341
267
  try {
342
- passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
343
- passphrase = passphrase.trim(); // Ensure no extra characters
344
- this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
268
+ passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
269
+ passphrase = passphrase.trim();
270
+ this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
345
271
  }
346
272
  catch (error) {
347
- this.log.error(`Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}: ${error}`);
273
+ this.log.error(`Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}: ${error}`);
348
274
  this.emit('server_error', error);
349
275
  return;
350
276
  }
351
277
  httpsServerOptions = { pfx, passphrase };
352
278
  }
353
279
  else {
354
- // 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.
355
280
  try {
356
- cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
357
- this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
281
+ cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
282
+ this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
358
283
  }
359
284
  catch (error) {
360
- this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
285
+ this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}: ${error}`);
361
286
  this.emit('server_error', error);
362
287
  return;
363
288
  }
364
289
  try {
365
- key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
366
- this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}`);
290
+ key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem'), 'utf8');
291
+ this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}`);
367
292
  }
368
293
  catch (error) {
369
- this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
294
+ this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'key.pem')}: ${error}`);
370
295
  this.emit('server_error', error);
371
296
  return;
372
297
  }
373
298
  try {
374
- ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
299
+ ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem'), 'utf8');
375
300
  fullChain = `${cert}\n${ca}`;
376
- this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')}`);
301
+ this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')}`);
377
302
  }
378
303
  catch (error) {
379
- this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
304
+ this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'ca.pem')} not loaded: ${error}`);
380
305
  }
381
306
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
382
307
  }
383
308
  if (hasParameter('mtls')) {
384
- httpsServerOptions.requestCert = true; // Request client certificate
385
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
309
+ httpsServerOptions.requestCert = true;
310
+ httpsServerOptions.rejectUnauthorized = true;
386
311
  }
387
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
388
312
  const https = await import('node:https');
389
313
  try {
390
314
  this.log.debug(`Creating HTTPS server...`);
@@ -395,7 +319,6 @@ export class Frontend extends EventEmitter {
395
319
  this.emit('server_error', error);
396
320
  return;
397
321
  }
398
- // Listen on the specified port
399
322
  if (hasParameter('ingress')) {
400
323
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
401
324
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -415,29 +338,23 @@ export class Frontend extends EventEmitter {
415
338
  }
416
339
  this.httpsServer.on('upgrade', async (req, socket, head) => {
417
340
  try {
418
- // Only proceed for real WebSocket upgrades
419
- // istanbul ignore next cause is only a safety check
420
341
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
421
342
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
422
343
  return socket.destroy();
423
344
  }
424
- // Build a URL so we can read ?password=...
425
345
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
426
- // Validate WebSocket password
427
346
  const password = url.searchParams.get('password') ?? '';
428
347
  if (password !== this.storedPassword) {
429
348
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
430
349
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
431
350
  return socket.destroy();
432
351
  }
433
- // Complete the WebSocket handshake
434
352
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
435
353
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
436
354
  this.webSocketServer?.emit('connection', ws, req);
437
355
  });
438
356
  }
439
357
  catch (err) {
440
- /* istanbul ignore next: only triggered on unexpected internal error */
441
358
  {
442
359
  inspectError(this.log, 'WebSocket upgrade error:', err);
443
360
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -459,7 +376,6 @@ export class Frontend extends EventEmitter {
459
376
  return;
460
377
  });
461
378
  }
462
- // Subscribe to cli events
463
379
  cliEmitter.removeAllListeners();
464
380
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
465
381
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -470,8 +386,6 @@ export class Frontend extends EventEmitter {
470
386
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
471
387
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
472
388
  });
473
- // Endpoint to validate login code
474
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
475
389
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
476
390
  const { password } = req.body;
477
391
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -484,20 +398,17 @@ export class Frontend extends EventEmitter {
484
398
  res.json({ valid: false });
485
399
  }
486
400
  });
487
- // Endpoint to provide health check for docker
488
401
  this.expressApp.get('/health', (req, res) => {
489
402
  this.log.debug('Express received /health');
490
403
  const healthStatus = {
491
- status: 'ok', // Indicate service is healthy
492
- uptime: process.uptime(), // Server uptime in seconds
493
- timestamp: new Date().toISOString(), // Current timestamp
404
+ status: 'ok',
405
+ uptime: process.uptime(),
406
+ timestamp: new Date().toISOString(),
494
407
  };
495
408
  res.status(200).json(healthStatus);
496
409
  });
497
- // Endpoint to provide memory usage details
498
410
  this.expressApp.get('/memory', async (req, res) => {
499
411
  this.log.debug('Express received /memory');
500
- // Memory usage from process
501
412
  const memoryUsageRaw = process.memoryUsage();
502
413
  const memoryUsage = {
503
414
  rss: formatBytes(memoryUsageRaw.rss),
@@ -506,13 +417,10 @@ export class Frontend extends EventEmitter {
506
417
  external: formatBytes(memoryUsageRaw.external),
507
418
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
508
419
  };
509
- // V8 heap statistics
510
420
  const { default: v8 } = await import('node:v8');
511
421
  const heapStatsRaw = v8.getHeapStatistics();
512
422
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
513
- // Format heapStats
514
423
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
515
- // Format heapSpaces
516
424
  const heapSpaces = heapSpacesRaw.map((space) => ({
517
425
  ...space,
518
426
  space_size: formatBytes(space.space_size),
@@ -531,22 +439,18 @@ export class Frontend extends EventEmitter {
531
439
  };
532
440
  res.status(200).json(memoryReport);
533
441
  });
534
- // Endpoint to provide settings
535
442
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
536
443
  this.log.debug('The frontend sent /api/settings');
537
444
  res.json(await this.getApiSettings());
538
445
  });
539
- // Endpoint to provide plugins
540
446
  this.expressApp.get('/api/plugins', async (req, res) => {
541
447
  this.log.debug('The frontend sent /api/plugins');
542
448
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
543
449
  });
544
- // Endpoint to provide devices
545
450
  this.expressApp.get('/api/devices', async (req, res) => {
546
451
  this.log.debug('The frontend sent /api/devices');
547
452
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
548
453
  });
549
- // Endpoint to view the matterbridge log
550
454
  this.expressApp.get('/api/view-mblog', async (req, res) => {
551
455
  this.log.debug('The frontend sent /api/view-mblog');
552
456
  try {
@@ -560,7 +464,6 @@ export class Frontend extends EventEmitter {
560
464
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
561
465
  }
562
466
  });
563
- // Endpoint to view the matter.js log
564
467
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
565
468
  this.log.debug('The frontend sent /api/view-mjlog');
566
469
  try {
@@ -574,7 +477,6 @@ export class Frontend extends EventEmitter {
574
477
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
575
478
  }
576
479
  });
577
- // Endpoint to view the diagnostic.log
578
480
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
579
481
  this.log.debug('The frontend sent /api/view-diagnostic');
580
482
  await this.generateDiagnostic();
@@ -585,13 +487,10 @@ export class Frontend extends EventEmitter {
585
487
  res.send(data.slice(29));
586
488
  }
587
489
  catch (error) {
588
- // istanbul ignore next
589
490
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
590
- // istanbul ignore next
591
491
  res.status(500).send('Error reading diagnostic log file.');
592
492
  }
593
493
  });
594
- // Endpoint to download the diagnostic.log
595
494
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
596
495
  this.log.debug(`The frontend sent /api/download-diagnostic`);
597
496
  await this.generateDiagnostic();
@@ -602,19 +501,16 @@ export class Frontend extends EventEmitter {
602
501
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
603
502
  }
604
503
  catch (error) {
605
- // istanbul ignore next
606
504
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
607
505
  }
608
506
  res.type('text/plain');
609
507
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
610
- /* istanbul ignore if */
611
508
  if (error) {
612
509
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
613
510
  res.status(500).send('Error downloading the diagnostic log file');
614
511
  }
615
512
  });
616
513
  });
617
- // Endpoint to view the history.html
618
514
  this.expressApp.get('/api/viewhistory', async (req, res) => {
619
515
  this.log.debug('The frontend sent /api/viewhistory');
620
516
  try {
@@ -628,7 +524,6 @@ export class Frontend extends EventEmitter {
628
524
  res.status(500).send('Error reading history file.');
629
525
  }
630
526
  });
631
- // Endpoint to download the history.html
632
527
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
633
528
  this.log.debug(`The frontend sent /api/downloadhistory`);
634
529
  try {
@@ -638,7 +533,6 @@ export class Frontend extends EventEmitter {
638
533
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
639
534
  res.type('text/plain');
640
535
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
641
- /* istanbul ignore if */
642
536
  if (error) {
643
537
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
644
538
  res.status(500).send('Error downloading history file');
@@ -650,7 +544,6 @@ export class Frontend extends EventEmitter {
650
544
  res.status(500).send('Error reading history file.');
651
545
  }
652
546
  });
653
- // Endpoint to view the shelly log
654
547
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
655
548
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
656
549
  try {
@@ -664,7 +557,6 @@ export class Frontend extends EventEmitter {
664
557
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
665
558
  }
666
559
  });
667
- // Endpoint to download the matterbridge log
668
560
  this.expressApp.get('/api/download-mblog', async (req, res) => {
669
561
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
670
562
  const fs = await import('node:fs');
@@ -679,14 +571,12 @@ export class Frontend extends EventEmitter {
679
571
  }
680
572
  res.type('text/plain');
681
573
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
682
- /* istanbul ignore if */
683
574
  if (error) {
684
575
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
685
576
  res.status(500).send('Error downloading the matterbridge log file');
686
577
  }
687
578
  });
688
579
  });
689
- // Endpoint to download the matter log
690
580
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
691
581
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
692
582
  const fs = await import('node:fs');
@@ -701,14 +591,12 @@ export class Frontend extends EventEmitter {
701
591
  }
702
592
  res.type('text/plain');
703
593
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
704
- /* istanbul ignore if */
705
594
  if (error) {
706
595
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
707
596
  res.status(500).send('Error downloading the matter log file');
708
597
  }
709
598
  });
710
599
  });
711
- // Endpoint to download the shelly log
712
600
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
713
601
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
714
602
  const fs = await import('node:fs');
@@ -723,91 +611,75 @@ export class Frontend extends EventEmitter {
723
611
  }
724
612
  res.type('text/plain');
725
613
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
726
- /* istanbul ignore if */
727
614
  if (error) {
728
615
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
729
616
  res.status(500).send('Error downloading Shelly system log file');
730
617
  }
731
618
  });
732
619
  });
733
- // Endpoint to download the matterbridge storage directory
734
620
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
735
621
  this.log.debug('The frontend sent /api/download-mbstorage');
736
622
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
737
623
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
738
- /* istanbul ignore if */
739
624
  if (error) {
740
625
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
741
626
  res.status(500).send('Error downloading the matterbridge storage file');
742
627
  }
743
628
  });
744
629
  });
745
- // Endpoint to download the matter storage file
746
630
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
747
631
  this.log.debug('The frontend sent /api/download-mjstorage');
748
632
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
749
633
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
750
- /* istanbul ignore if */
751
634
  if (error) {
752
635
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
753
636
  res.status(500).send('Error downloading the matter storage zip file');
754
637
  }
755
638
  });
756
639
  });
757
- // Endpoint to download the matterbridge plugin directory
758
640
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
759
641
  this.log.debug('The frontend sent /api/download-pluginstorage');
760
642
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
761
643
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
762
- /* istanbul ignore if */
763
644
  if (error) {
764
645
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
765
646
  res.status(500).send('Error downloading the matterbridge plugin storage file');
766
647
  }
767
648
  });
768
649
  });
769
- // Endpoint to download the matterbridge plugin config files
770
650
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
771
651
  this.log.debug('The frontend sent /api/download-pluginconfig');
772
652
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
773
653
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
774
- /* istanbul ignore if */
775
654
  if (error) {
776
655
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
777
656
  res.status(500).send('Error downloading the matterbridge plugin config file');
778
657
  }
779
658
  });
780
659
  });
781
- // Endpoint to download the matterbridge backup (created with the backup command)
782
660
  this.expressApp.get('/api/download-backup', async (req, res) => {
783
661
  this.log.debug('The frontend sent /api/download-backup');
784
662
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
785
- /* istanbul ignore if */
786
663
  if (error) {
787
664
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
788
665
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
789
666
  }
790
667
  });
791
668
  });
792
- // Endpoint to upload a package
793
669
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
794
670
  const { filename } = req.body;
795
671
  const file = req.file;
796
- /* istanbul ignore if */
797
672
  if (!file || !filename) {
798
673
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
799
674
  res.status(400).send('Invalid request: file and filename are required');
800
675
  return;
801
676
  }
802
677
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
803
- // Define the path where the plugin file will be saved
804
678
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
805
679
  try {
806
- // Move the uploaded file to the specified path
807
680
  const fs = await import('node:fs');
808
681
  await fs.promises.rename(file.path, filePath);
809
682
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
810
- // Install the plugin package
811
683
  if (filename.endsWith('.tgz')) {
812
684
  const { spawnCommand } = await import('./utils/spawn.js');
813
685
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -827,26 +699,23 @@ export class Frontend extends EventEmitter {
827
699
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
828
700
  }
829
701
  });
830
- // Fallback for routing (must be the last route)
831
702
  this.expressApp.use((req, res) => {
832
- this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
833
- res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
703
+ const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
704
+ this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
705
+ res.sendFile('index.html', { root: filePath });
834
706
  });
835
- this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
707
+ this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend', 'build')}${UNDERLINEOFF}${rs}`);
836
708
  }
837
709
  async stop() {
838
710
  this.log.debug('Stopping the frontend...');
839
711
  const ws = await import('ws');
840
- // Remove listeners from the express app
841
712
  if (this.expressApp) {
842
713
  this.expressApp.removeAllListeners();
843
714
  this.expressApp = undefined;
844
715
  this.log.debug('Frontend app closed successfully');
845
716
  }
846
- // Close the WebSocket server
847
717
  if (this.webSocketServer) {
848
718
  this.log.debug('Closing WebSocket server...');
849
- // Close all active connections
850
719
  this.webSocketServer.clients.forEach((client) => {
851
720
  if (client.readyState === ws.WebSocket.OPEN) {
852
721
  client.close();
@@ -855,7 +724,6 @@ export class Frontend extends EventEmitter {
855
724
  await withTimeout(new Promise((resolve) => {
856
725
  this.webSocketServer?.close((error) => {
857
726
  if (error) {
858
- // istanbul ignore next
859
727
  this.log.error(`Error closing WebSocket server: ${error}`);
860
728
  }
861
729
  else {
@@ -868,27 +736,8 @@ export class Frontend extends EventEmitter {
868
736
  this.webSocketServer.removeAllListeners();
869
737
  this.webSocketServer = undefined;
870
738
  }
871
- // Close the http server
872
739
  if (this.httpServer) {
873
740
  this.log.debug('Closing http server...');
874
- /*
875
- await withTimeout(
876
- new Promise<void>((resolve) => {
877
- this.httpServer?.close((error) => {
878
- if (error) {
879
- // istanbul ignore next
880
- this.log.error(`Error closing http server: ${error}`);
881
- } else {
882
- this.log.debug('Http server closed successfully');
883
- this.emit('server_stopped');
884
- }
885
- resolve();
886
- });
887
- }),
888
- 5000,
889
- false,
890
- );
891
- */
892
741
  this.httpServer.close();
893
742
  this.log.debug('Http server closed successfully');
894
743
  this.listening = false;
@@ -897,27 +746,8 @@ export class Frontend extends EventEmitter {
897
746
  this.httpServer = undefined;
898
747
  this.log.debug('Frontend http server closed successfully');
899
748
  }
900
- // Close the https server
901
749
  if (this.httpsServer) {
902
750
  this.log.debug('Closing https server...');
903
- /*
904
- await withTimeout(
905
- new Promise<void>((resolve) => {
906
- this.httpsServer?.close((error) => {
907
- if (error) {
908
- // istanbul ignore next
909
- this.log.error(`Error closing https server: ${error}`);
910
- } else {
911
- this.log.debug('Https server closed successfully');
912
- this.emit('server_stopped');
913
- }
914
- resolve();
915
- });
916
- }),
917
- 5000,
918
- false,
919
- );
920
- */
921
751
  this.httpsServer.close();
922
752
  this.log.debug('Https server closed successfully');
923
753
  this.listening = false;
@@ -928,13 +758,7 @@ export class Frontend extends EventEmitter {
928
758
  }
929
759
  this.log.debug('Frontend stopped successfully');
930
760
  }
931
- /**
932
- * Retrieves the api settings data.
933
- *
934
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
935
- */
936
761
  async getApiSettings() {
937
- // Update the variable system information properties
938
762
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
939
763
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
940
764
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -944,7 +768,6 @@ export class Frontend extends EventEmitter {
944
768
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
945
769
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
946
770
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
947
- // Create the matterbridge information
948
771
  const info = {
949
772
  homeDirectory: this.matterbridge.homeDirectory,
950
773
  rootDirectory: this.matterbridge.rootDirectory,
@@ -980,15 +803,9 @@ export class Frontend extends EventEmitter {
980
803
  };
981
804
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
982
805
  }
983
- /**
984
- * Retrieves the reachable attribute.
985
- *
986
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
987
- * @returns {boolean} The reachable attribute.
988
- */
989
806
  getReachability(device) {
990
807
  if (this.matterbridge.hasCleanupStarted)
991
- return false; // Skip if cleanup has started
808
+ return false;
992
809
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
993
810
  return false;
994
811
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -999,15 +816,9 @@ export class Frontend extends EventEmitter {
999
816
  return true;
1000
817
  return false;
1001
818
  }
1002
- /**
1003
- * Retrieves the power source attribute.
1004
- *
1005
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1006
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1007
- */
1008
819
  getPowerSource(endpoint) {
1009
820
  if (this.matterbridge.hasCleanupStarted)
1010
- return; // Skip if cleanup has started
821
+ return;
1011
822
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1012
823
  return undefined;
1013
824
  const powerSource = (device) => {
@@ -1022,25 +833,16 @@ export class Frontend extends EventEmitter {
1022
833
  }
1023
834
  return;
1024
835
  };
1025
- // Root endpoint
1026
836
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1027
837
  return powerSource(endpoint);
1028
- // Child endpoints
1029
838
  for (const child of endpoint.getChildEndpoints()) {
1030
839
  if (child.hasClusterServer(PowerSource.Cluster.id))
1031
840
  return powerSource(child);
1032
841
  }
1033
842
  }
1034
- /**
1035
- * Retrieves the cluster text description from a given device.
1036
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1037
- *
1038
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1039
- * @returns {string} The attributes description of the cluster servers in the device.
1040
- */
1041
843
  getClusterTextFromDevice(device) {
1042
844
  if (this.matterbridge.hasCleanupStarted)
1043
- return ''; // Skip if cleanup has started
845
+ return '';
1044
846
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1045
847
  return '';
1046
848
  const getUserLabel = (device) => {
@@ -1050,7 +852,6 @@ export class Frontend extends EventEmitter {
1050
852
  if (composed)
1051
853
  return 'Composed: ' + composed.value;
1052
854
  }
1053
- // istanbul ignore next cause is not reachable
1054
855
  return '';
1055
856
  };
1056
857
  const getFixedLabel = (device) => {
@@ -1060,13 +861,11 @@ export class Frontend extends EventEmitter {
1060
861
  if (composed)
1061
862
  return 'Composed: ' + composed.value;
1062
863
  }
1063
- // istanbul ignore next cause is not reacheable
1064
864
  return '';
1065
865
  };
1066
866
  let attributes = '';
1067
867
  let supportedModes = [];
1068
868
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1069
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1070
869
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1071
870
  return;
1072
871
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1156,17 +955,11 @@ export class Frontend extends EventEmitter {
1156
955
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1157
956
  attributes += `${getUserLabel(device)} `;
1158
957
  });
1159
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1160
958
  return attributes.trimStart().trimEnd();
1161
959
  }
1162
- /**
1163
- * Retrieves the registered plugins sanitized for res.json().
1164
- *
1165
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1166
- */
1167
960
  getPlugins() {
1168
961
  if (this.matterbridge.hasCleanupStarted)
1169
- return []; // Skip if cleanup has started
962
+ return [];
1170
963
  const plugins = [];
1171
964
  for (const plugin of this.matterbridge.plugins.array()) {
1172
965
  plugins.push({
@@ -1194,27 +987,18 @@ export class Frontend extends EventEmitter {
1194
987
  schemaJson: plugin.schemaJson,
1195
988
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1196
989
  hasBlackList: plugin.configJson?.blackList !== undefined,
1197
- // Childbridge mode specific data
1198
990
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1199
991
  });
1200
992
  }
1201
993
  return plugins;
1202
994
  }
1203
- /**
1204
- * Retrieves the devices from Matterbridge.
1205
- *
1206
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1207
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1208
- */
1209
995
  getDevices(pluginName) {
1210
996
  if (this.matterbridge.hasCleanupStarted)
1211
- return []; // Skip if cleanup has started
997
+ return [];
1212
998
  const devices = [];
1213
999
  for (const device of this.matterbridge.devices.array()) {
1214
- // Filter by pluginName if provided
1215
1000
  if (pluginName && pluginName !== device.plugin)
1216
1001
  continue;
1217
- // Check if the device has the required properties
1218
1002
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1219
1003
  continue;
1220
1004
  devices.push({
@@ -1234,39 +1018,24 @@ export class Frontend extends EventEmitter {
1234
1018
  }
1235
1019
  return devices;
1236
1020
  }
1237
- /**
1238
- * Retrieves the clusters from a given plugin and endpoint number.
1239
- *
1240
- * Response for /api/clusters
1241
- *
1242
- * @param {string} pluginName - The name of the plugin.
1243
- * @param {number} endpointNumber - The endpoint number.
1244
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1245
- */
1246
1021
  getClusters(pluginName, endpointNumber) {
1247
1022
  if (this.matterbridge.hasCleanupStarted)
1248
- return; // Skip if cleanup has started
1023
+ return;
1249
1024
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1250
1025
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1251
1026
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1252
1027
  return;
1253
1028
  }
1254
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1255
- // Get the device types from the main endpoint
1256
1029
  const deviceTypes = [];
1257
1030
  const clusters = [];
1258
1031
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1259
1032
  deviceTypes.push(d.deviceType);
1260
1033
  });
1261
- // Get the clusters from the main endpoint
1262
1034
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1263
1035
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1264
1036
  return;
1265
1037
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1266
1038
  return;
1267
- // console.log(
1268
- // `${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}`,
1269
- // );
1270
1039
  clusters.push({
1271
1040
  endpoint: endpoint.number.toString(),
1272
1041
  number: endpoint.number,
@@ -1280,19 +1049,12 @@ export class Frontend extends EventEmitter {
1280
1049
  attributeLocalValue: attributeValue,
1281
1050
  });
1282
1051
  });
1283
- // Get the child endpoints
1284
1052
  const childEndpoints = endpoint.getChildEndpoints();
1285
- // if (childEndpoints.length === 0) {
1286
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1287
- // }
1288
1053
  childEndpoints.forEach((childEndpoint) => {
1289
- // istanbul ignore if cause is not reachable: should never happen but ...
1290
1054
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1291
1055
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1292
1056
  return;
1293
1057
  }
1294
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1295
- // Get the device types of the child endpoint
1296
1058
  const deviceTypes = [];
1297
1059
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1298
1060
  deviceTypes.push(d.deviceType);
@@ -1302,9 +1064,6 @@ export class Frontend extends EventEmitter {
1302
1064
  return;
1303
1065
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1304
1066
  return;
1305
- // console.log(
1306
- // `${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}`,
1307
- // );
1308
1067
  clusters.push({
1309
1068
  endpoint: childEndpoint.number.toString(),
1310
1069
  number: childEndpoint.number,
@@ -1324,7 +1083,6 @@ export class Frontend extends EventEmitter {
1324
1083
  async generateDiagnostic() {
1325
1084
  this.log.debug('Generating diagnostic...');
1326
1085
  const serverNodes = [];
1327
- // istanbul ignore else
1328
1086
  if (this.matterbridge.bridgeMode === 'bridge') {
1329
1087
  if (this.matterbridge.serverNode)
1330
1088
  serverNodes.push(this.matterbridge.serverNode);
@@ -1335,7 +1093,6 @@ export class Frontend extends EventEmitter {
1335
1093
  serverNodes.push(plugin.serverNode);
1336
1094
  }
1337
1095
  }
1338
- // istanbul ignore next
1339
1096
  for (const device of this.matterbridge.devices.array()) {
1340
1097
  if (device.serverNode)
1341
1098
  serverNodes.push(device.serverNode);
@@ -1359,15 +1116,8 @@ export class Frontend extends EventEmitter {
1359
1116
  values: [...serverNodes],
1360
1117
  })));
1361
1118
  delete Logger.destinations.diagnostic;
1362
- await wait(500); // Wait for the log to be written
1119
+ await wait(500);
1363
1120
  }
1364
- /**
1365
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1366
- *
1367
- * @param {WebSocket} client - The websocket client that sent the message.
1368
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1369
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1370
- */
1371
1121
  async wsMessageHandler(client, message) {
1372
1122
  let data;
1373
1123
  const sendResponse = (data) => {
@@ -1387,7 +1137,7 @@ export class Frontend extends EventEmitter {
1387
1137
  };
1388
1138
  try {
1389
1139
  data = JSON.parse(message.toString());
1390
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1140
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1391
1141
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1392
1142
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1393
1143
  return;
@@ -1461,7 +1211,6 @@ export class Frontend extends EventEmitter {
1461
1211
  return;
1462
1212
  })
1463
1213
  .catch((_error) => {
1464
- //
1465
1214
  });
1466
1215
  }
1467
1216
  else {
@@ -1509,7 +1258,6 @@ export class Frontend extends EventEmitter {
1509
1258
  return;
1510
1259
  })
1511
1260
  .catch((_error) => {
1512
- //
1513
1261
  });
1514
1262
  }
1515
1263
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1535,7 +1283,6 @@ export class Frontend extends EventEmitter {
1535
1283
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1536
1284
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1537
1285
  if (plugin.serverNode) {
1538
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1539
1286
  await this.matterbridge.stopServerNode(plugin.serverNode);
1540
1287
  plugin.serverNode = undefined;
1541
1288
  }
@@ -1545,20 +1292,18 @@ export class Frontend extends EventEmitter {
1545
1292
  this.matterbridge.devices.remove(device);
1546
1293
  }
1547
1294
  }
1548
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1549
1295
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1550
1296
  await this.matterbridge.createDynamicPlugin(plugin);
1551
1297
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1552
- plugin.restartRequired = false; // Reset plugin restartRequired
1298
+ plugin.restartRequired = false;
1553
1299
  let needRestart = 0;
1554
1300
  for (const plugin of this.matterbridge.plugins) {
1555
1301
  if (plugin.restartRequired)
1556
1302
  needRestart++;
1557
1303
  }
1558
1304
  if (needRestart === 0) {
1559
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1305
+ this.wssSendRestartNotRequired(true);
1560
1306
  }
1561
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1562
1307
  if (plugin.serverNode)
1563
1308
  await this.matterbridge.startServerNode(plugin.serverNode);
1564
1309
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1824,22 +1569,22 @@ export class Frontend extends EventEmitter {
1824
1569
  if (isValidString(data.params.value, 4)) {
1825
1570
  this.log.debug('Matterbridge logger level:', data.params.value);
1826
1571
  if (data.params.value === 'Debug') {
1827
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1572
+ await this.matterbridge.setLogLevel("debug");
1828
1573
  }
1829
1574
  else if (data.params.value === 'Info') {
1830
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1575
+ await this.matterbridge.setLogLevel("info");
1831
1576
  }
1832
1577
  else if (data.params.value === 'Notice') {
1833
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1578
+ await this.matterbridge.setLogLevel("notice");
1834
1579
  }
1835
1580
  else if (data.params.value === 'Warn') {
1836
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1581
+ await this.matterbridge.setLogLevel("warn");
1837
1582
  }
1838
1583
  else if (data.params.value === 'Error') {
1839
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1584
+ await this.matterbridge.setLogLevel("error");
1840
1585
  }
1841
1586
  else if (data.params.value === 'Fatal') {
1842
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1587
+ await this.matterbridge.setLogLevel("fatal");
1843
1588
  }
1844
1589
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1845
1590
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1850,7 +1595,6 @@ export class Frontend extends EventEmitter {
1850
1595
  this.log.debug('Matterbridge file log:', data.params.value);
1851
1596
  this.matterbridge.fileLogger = data.params.value;
1852
1597
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1853
- // Create the file logger for matterbridge
1854
1598
  if (data.params.value)
1855
1599
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1856
1600
  else
@@ -1879,12 +1623,11 @@ export class Frontend extends EventEmitter {
1879
1623
  else if (data.params.value === 'Fatal') {
1880
1624
  Logger.level = MatterLogLevel.FATAL;
1881
1625
  }
1882
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1883
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1884
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1885
- callbackLogLevel = "info" /* LogLevel.INFO */;
1886
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1887
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1626
+ let callbackLogLevel = "notice";
1627
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1628
+ callbackLogLevel = "info";
1629
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1630
+ callbackLogLevel = "debug";
1888
1631
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1889
1632
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1890
1633
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -1936,7 +1679,6 @@ export class Frontend extends EventEmitter {
1936
1679
  }
1937
1680
  break;
1938
1681
  case 'setmatterport':
1939
- // eslint-disable-next-line no-case-declarations
1940
1682
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1941
1683
  if (isValidNumber(port, 5540, 5600)) {
1942
1684
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1956,7 +1698,6 @@ export class Frontend extends EventEmitter {
1956
1698
  }
1957
1699
  break;
1958
1700
  case 'setmatterdiscriminator':
1959
- // eslint-disable-next-line no-case-declarations
1960
1701
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1961
1702
  if (isValidNumber(discriminator, 0, 4095)) {
1962
1703
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1976,7 +1717,6 @@ export class Frontend extends EventEmitter {
1976
1717
  }
1977
1718
  break;
1978
1719
  case 'setmatterpasscode':
1979
- // eslint-disable-next-line no-case-declarations
1980
1720
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1981
1721
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1982
1722
  this.matterbridge.passcode = passcode;
@@ -2022,19 +1762,15 @@ export class Frontend extends EventEmitter {
2022
1762
  return;
2023
1763
  }
2024
1764
  const config = plugin.configJson;
2025
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2026
1765
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2027
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2028
1766
  if (select === 'serial')
2029
1767
  this.log.info(`Selected device serial ${data.params.serial}`);
2030
1768
  if (select === 'name')
2031
1769
  this.log.info(`Selected device name ${data.params.name}`);
2032
1770
  if (config && select && (select === 'serial' || select === 'name')) {
2033
- // Remove postfix from the serial if it exists
2034
1771
  if (config.postfix) {
2035
1772
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2036
1773
  }
2037
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2038
1774
  if (isValidArray(config.whiteList, 1)) {
2039
1775
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2040
1776
  config.whiteList.push(data.params.serial);
@@ -2043,7 +1779,6 @@ export class Frontend extends EventEmitter {
2043
1779
  config.whiteList.push(data.params.name);
2044
1780
  }
2045
1781
  }
2046
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2047
1782
  if (isValidArray(config.blackList, 1)) {
2048
1783
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2049
1784
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2071,9 +1806,7 @@ export class Frontend extends EventEmitter {
2071
1806
  return;
2072
1807
  }
2073
1808
  const config = plugin.configJson;
2074
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2075
1809
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2076
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2077
1810
  if (select === 'serial')
2078
1811
  this.log.info(`Unselected device serial ${data.params.serial}`);
2079
1812
  if (select === 'name')
@@ -2082,7 +1815,6 @@ export class Frontend extends EventEmitter {
2082
1815
  if (config.postfix) {
2083
1816
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2084
1817
  }
2085
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2086
1818
  if (isValidArray(config.whiteList, 1)) {
2087
1819
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2088
1820
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2091,7 +1823,6 @@ export class Frontend extends EventEmitter {
2091
1823
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2092
1824
  }
2093
1825
  }
2094
- // Add the serial to the blackList
2095
1826
  if (isValidArray(config.blackList)) {
2096
1827
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2097
1828
  config.blackList.push(data.params.serial);
@@ -2114,7 +1845,6 @@ export class Frontend extends EventEmitter {
2114
1845
  }
2115
1846
  }
2116
1847
  else {
2117
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2118
1848
  const localData = data;
2119
1849
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2120
1850
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2124,46 +1854,23 @@ export class Frontend extends EventEmitter {
2124
1854
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2125
1855
  }
2126
1856
  }
2127
- /**
2128
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2129
- *
2130
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2131
- * @param {string} time - The time string of the message
2132
- * @param {string} name - The logger name of the message
2133
- * @param {string} message - The content of the message.
2134
- *
2135
- * @remarks
2136
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2137
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2138
- * The function sends the message to all connected clients.
2139
- */
2140
1857
  wssSendLogMessage(level, time, name, message) {
2141
1858
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2142
1859
  return;
2143
1860
  if (!level || !time || !name || !message)
2144
1861
  return;
2145
- // Remove ANSI escape codes from the message
2146
- // eslint-disable-next-line no-control-regex
2147
1862
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2148
- // Remove leading asterisks from the message
2149
1863
  message = message.replace(/^\*+/, '');
2150
- // Replace all occurrences of \t and \n
2151
1864
  message = message.replace(/[\t\n]/g, '');
2152
- // Remove non-printable characters
2153
- // eslint-disable-next-line no-control-regex
2154
1865
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2155
- // Replace all occurrences of \" with "
2156
1866
  message = message.replace(/\\"/g, '"');
2157
- // Define the maximum allowed length for continuous characters without a space
2158
1867
  const maxContinuousLength = 100;
2159
1868
  const keepStartLength = 20;
2160
1869
  const keepEndLength = 20;
2161
- // Split the message into words
2162
1870
  if (level !== 'spawn') {
2163
1871
  message = message
2164
1872
  .split(' ')
2165
1873
  .map((word) => {
2166
- // If the word length exceeds the max continuous length, insert spaces and truncate
2167
1874
  if (word.length > maxContinuousLength) {
2168
1875
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2169
1876
  }
@@ -2171,34 +1878,14 @@ export class Frontend extends EventEmitter {
2171
1878
  })
2172
1879
  .join(' ');
2173
1880
  }
2174
- // Send the message to all connected clients
2175
1881
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2176
1882
  }
2177
- /**
2178
- * Sends a need to refresh WebSocket message to all connected clients.
2179
- *
2180
- * @param {string} changed - The changed value.
2181
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2182
- * possible values for changed:
2183
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2184
- * - 'plugins'
2185
- * - 'devices'
2186
- * - 'matter' with param 'matter' (QRDiv component)
2187
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2188
- */
2189
1883
  wssSendRefreshRequired(changed, params) {
2190
1884
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2191
1885
  return;
2192
1886
  this.log.debug('Sending a refresh required message to all connected clients');
2193
- // Send the message to all connected clients
2194
1887
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2195
1888
  }
2196
- /**
2197
- * Sends a need to restart WebSocket message to all connected clients.
2198
- *
2199
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2200
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2201
- */
2202
1889
  wssSendRestartRequired(snackbar = true, fixed = false) {
2203
1890
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2204
1891
  return;
@@ -2207,14 +1894,8 @@ export class Frontend extends EventEmitter {
2207
1894
  this.matterbridge.fixedRestartRequired = fixed;
2208
1895
  if (snackbar === true)
2209
1896
  this.wssSendSnackbarMessage(`Restart required`, 0);
2210
- // Send the message to all connected clients
2211
1897
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2212
1898
  }
2213
- /**
2214
- * Sends a no need to restart WebSocket message to all connected clients.
2215
- *
2216
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2217
- */
2218
1899
  wssSendRestartNotRequired(snackbar = true) {
2219
1900
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2220
1901
  return;
@@ -2222,133 +1903,57 @@ export class Frontend extends EventEmitter {
2222
1903
  this.matterbridge.restartRequired = false;
2223
1904
  if (snackbar === true)
2224
1905
  this.wssSendCloseSnackbarMessage(`Restart required`);
2225
- // Send the message to all connected clients
2226
1906
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2227
1907
  }
2228
- /**
2229
- * Sends a need to update WebSocket message to all connected clients.
2230
- *
2231
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2232
- */
2233
1908
  wssSendUpdateRequired(devVersion = false) {
2234
1909
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2235
1910
  return;
2236
1911
  this.log.debug('Sending an update required message to all connected clients');
2237
1912
  this.matterbridge.updateRequired = true;
2238
- // Send the message to all connected clients
2239
1913
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2240
1914
  }
2241
- /**
2242
- * Sends a cpu update message to all connected clients.
2243
- *
2244
- * @param {number} cpuUsage - The CPU usage percentage to send.
2245
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2246
- */
2247
1915
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2248
1916
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2249
1917
  return;
2250
1918
  if (hasParameter('debug'))
2251
1919
  this.log.debug('Sending a cpu update message to all connected clients');
2252
- // Send the message to all connected clients
2253
1920
  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 } });
2254
1921
  }
2255
- /**
2256
- * Sends a memory update message to all connected clients.
2257
- *
2258
- * @param {string} totalMemory - The total memory in bytes.
2259
- * @param {string} freeMemory - The free memory in bytes.
2260
- * @param {string} rss - The resident set size in bytes.
2261
- * @param {string} heapTotal - The total heap memory in bytes.
2262
- * @param {string} heapUsed - The used heap memory in bytes.
2263
- * @param {string} external - The external memory in bytes.
2264
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2265
- */
2266
1922
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2267
1923
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2268
1924
  return;
2269
1925
  if (hasParameter('debug'))
2270
1926
  this.log.debug('Sending a memory update message to all connected clients');
2271
- // Send the message to all connected clients
2272
1927
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2273
1928
  }
2274
- /**
2275
- * Sends an uptime update message to all connected clients.
2276
- *
2277
- * @param {string} systemUptime - The system uptime in a human-readable format.
2278
- * @param {string} processUptime - The process uptime in a human-readable format.
2279
- */
2280
1929
  wssSendUptimeUpdate(systemUptime, processUptime) {
2281
1930
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2282
1931
  return;
2283
1932
  if (hasParameter('debug'))
2284
1933
  this.log.debug('Sending a uptime update message to all connected clients');
2285
- // Send the message to all connected clients
2286
1934
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2287
1935
  }
2288
- /**
2289
- * Sends an open snackbar message to all connected clients.
2290
- *
2291
- * @param {string} message - The message to send.
2292
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2293
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2294
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2295
- *
2296
- * @remarks
2297
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2298
- */
2299
1936
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2300
1937
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2301
1938
  return;
2302
1939
  this.log.debug('Sending a snackbar message to all connected clients');
2303
- // Send the message to all connected clients
2304
1940
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2305
1941
  }
2306
- /**
2307
- * Sends a close snackbar message to all connected clients.
2308
- * It will close the snackbar message with the same message and timeout = 0.
2309
- *
2310
- * @param {string} message - The message to send.
2311
- */
2312
1942
  wssSendCloseSnackbarMessage(message) {
2313
1943
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2314
1944
  return;
2315
1945
  this.log.debug('Sending a close snackbar message to all connected clients');
2316
- // Send the message to all connected clients
2317
1946
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2318
1947
  }
2319
- /**
2320
- * Sends an attribute update message to all connected WebSocket clients.
2321
- *
2322
- * @param {string | undefined} plugin - The name of the plugin.
2323
- * @param {string | undefined} serialNumber - The serial number of the device.
2324
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2325
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2326
- * @param {string} id - The endpoint id where the attribute belongs.
2327
- * @param {string} cluster - The cluster name where the attribute belongs.
2328
- * @param {string} attribute - The name of the attribute that changed.
2329
- * @param {number | string | boolean} value - The new value of the attribute.
2330
- *
2331
- * @remarks
2332
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2333
- * with the updated attribute information.
2334
- */
2335
1948
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2336
1949
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2337
1950
  return;
2338
1951
  this.log.debug('Sending an attribute update message to all connected clients');
2339
- // Send the message to all connected clients
2340
1952
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2341
1953
  }
2342
- /**
2343
- * Sends a message to all connected clients.
2344
- * This is an helper function to send a broadcast message to all connected clients.
2345
- *
2346
- * @param {WsMessageBroadcast} msg - The message to send.
2347
- */
2348
1954
  wssBroadcastMessage(msg) {
2349
1955
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2350
1956
  return;
2351
- // Send the message to all connected clients
2352
1957
  const stringifiedMsg = JSON.stringify(msg);
2353
1958
  if (msg.method !== 'log')
2354
1959
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -2359,4 +1964,3 @@ export class Frontend extends EventEmitter {
2359
1964
  });
2360
1965
  }
2361
1966
  }
2362
- //# sourceMappingURL=frontend.js.map