matterbridge 3.3.7 → 3.3.8-dev-20251115-920acfc

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