matterbridge 3.3.8 → 3.3.9-dev-20251118-930cfdb

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