matterbridge 3.4.0 → 3.4.1-dev-20251127-826b2bf

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 (332) hide show
  1. package/CHANGELOG.md +128 -112
  2. package/README-DEV.md +2 -2
  3. package/README-DOCKER.md +1 -1
  4. package/README-MACOS-PLIST.md +1 -1
  5. package/README-NGINX.md +1 -1
  6. package/README-PODMAN.md +1 -1
  7. package/README-SERVICE-LOCAL.md +1 -1
  8. package/README-SERVICE-OPT.md +6 -1
  9. package/README-SERVICE.md +1 -1
  10. package/README.md +57 -49
  11. package/dist/broadcastServer.js +1 -93
  12. package/dist/broadcastServerTypes.js +0 -24
  13. package/dist/cli.js +1 -97
  14. package/dist/cliEmitter.js +0 -37
  15. package/dist/cliHistory.js +0 -38
  16. package/dist/clusters/export.js +0 -2
  17. package/dist/defaultConfigSchema.js +0 -24
  18. package/dist/deviceManager.js +1 -113
  19. package/dist/devices/airConditioner.js +0 -57
  20. package/dist/devices/batteryStorage.js +1 -48
  21. package/dist/devices/cooktop.js +0 -56
  22. package/dist/devices/dishwasher.js +0 -57
  23. package/dist/devices/evse.js +10 -74
  24. package/dist/devices/export.js +0 -5
  25. package/dist/devices/extractorHood.js +0 -43
  26. package/dist/devices/heatPump.js +2 -50
  27. package/dist/devices/laundryDryer.js +3 -62
  28. package/dist/devices/laundryWasher.js +4 -70
  29. package/dist/devices/microwaveOven.js +5 -88
  30. package/dist/devices/oven.js +0 -85
  31. package/dist/devices/refrigerator.js +0 -102
  32. package/dist/devices/roboticVacuumCleaner.js +9 -100
  33. package/dist/devices/solarPower.js +0 -38
  34. package/dist/devices/speaker.js +0 -84
  35. package/dist/devices/temperatureControl.js +3 -24
  36. package/dist/devices/waterHeater.js +2 -82
  37. package/dist/dgram/coap.js +13 -126
  38. package/dist/dgram/dgram.js +2 -114
  39. package/dist/dgram/mb_coap.js +3 -41
  40. package/dist/dgram/mb_mdns.js +15 -80
  41. package/dist/dgram/mdns.js +137 -299
  42. package/dist/dgram/multicast.js +1 -62
  43. package/dist/dgram/unicast.js +0 -54
  44. package/dist/frontend.js +35 -455
  45. package/dist/frontendTypes.js +0 -45
  46. package/dist/helpers.js +0 -53
  47. package/dist/index.js +0 -25
  48. package/dist/jestutils/export.js +0 -1
  49. package/dist/jestutils/jestHelpers.js +13 -352
  50. package/dist/logger/export.js +0 -1
  51. package/dist/matter/behaviors.js +0 -2
  52. package/dist/matter/clusters.js +0 -2
  53. package/dist/matter/devices.js +0 -2
  54. package/dist/matter/endpoints.js +0 -2
  55. package/dist/matter/export.js +0 -3
  56. package/dist/matter/types.js +0 -3
  57. package/dist/matterNode.js +8 -369
  58. package/dist/matterbridge.js +74 -788
  59. package/dist/matterbridgeAccessoryPlatform.js +0 -38
  60. package/dist/matterbridgeBehaviors.js +5 -68
  61. package/dist/matterbridgeDeviceTypes.js +14 -635
  62. package/dist/matterbridgeDynamicPlatform.js +0 -38
  63. package/dist/matterbridgeEndpoint.js +53 -1444
  64. package/dist/matterbridgeEndpointHelpers.js +20 -483
  65. package/dist/matterbridgeEndpointTypes.js +0 -25
  66. package/dist/matterbridgePlatform.js +2 -460
  67. package/dist/matterbridgeTypes.js +0 -26
  68. package/dist/pluginManager.js +5 -340
  69. package/dist/shelly.js +7 -168
  70. package/dist/storage/export.js +0 -1
  71. package/dist/update.js +0 -69
  72. package/dist/utils/colorUtils.js +2 -97
  73. package/dist/utils/commandLine.js +0 -60
  74. package/dist/utils/copyDirectory.js +0 -37
  75. package/dist/utils/createDirectory.js +0 -33
  76. package/dist/utils/createZip.js +2 -47
  77. package/dist/utils/deepCopy.js +0 -39
  78. package/dist/utils/deepEqual.js +1 -72
  79. package/dist/utils/error.js +0 -41
  80. package/dist/utils/export.js +0 -1
  81. package/dist/utils/format.js +0 -49
  82. package/dist/utils/hex.js +0 -124
  83. package/dist/utils/inspector.js +1 -69
  84. package/dist/utils/isvalid.js +0 -101
  85. package/dist/utils/network.js +5 -96
  86. package/dist/utils/spawn.js +1 -71
  87. package/dist/utils/tracker.js +1 -64
  88. package/dist/utils/wait.js +8 -60
  89. package/frontend/build/assets/index.js +4 -4
  90. package/frontend/build/assets/vendor_mui.js +1 -1
  91. package/frontend/build/assets/vendor_node_modules.js +19 -83
  92. package/frontend/build/assets/vendor_qrcode.js +1 -9
  93. package/frontend/build/assets/vendor_rjsf.js +1 -9
  94. package/frontend/package-lock.json +229 -439
  95. package/frontend/package.json +15 -15
  96. package/marked.ps1 +15 -0
  97. package/npm-shrinkwrap.json +165 -231
  98. package/package.json +2 -3
  99. package/dist/broadcastServer.d.ts +0 -115
  100. package/dist/broadcastServer.d.ts.map +0 -1
  101. package/dist/broadcastServer.js.map +0 -1
  102. package/dist/broadcastServerTypes.d.ts +0 -838
  103. package/dist/broadcastServerTypes.d.ts.map +0 -1
  104. package/dist/broadcastServerTypes.js.map +0 -1
  105. package/dist/cli.d.ts +0 -30
  106. package/dist/cli.d.ts.map +0 -1
  107. package/dist/cli.js.map +0 -1
  108. package/dist/cliEmitter.d.ts +0 -50
  109. package/dist/cliEmitter.d.ts.map +0 -1
  110. package/dist/cliEmitter.js.map +0 -1
  111. package/dist/cliHistory.d.ts +0 -48
  112. package/dist/cliHistory.d.ts.map +0 -1
  113. package/dist/cliHistory.js.map +0 -1
  114. package/dist/clusters/export.d.ts +0 -2
  115. package/dist/clusters/export.d.ts.map +0 -1
  116. package/dist/clusters/export.js.map +0 -1
  117. package/dist/defaultConfigSchema.d.ts +0 -28
  118. package/dist/defaultConfigSchema.d.ts.map +0 -1
  119. package/dist/defaultConfigSchema.js.map +0 -1
  120. package/dist/deviceManager.d.ts +0 -135
  121. package/dist/deviceManager.d.ts.map +0 -1
  122. package/dist/deviceManager.js.map +0 -1
  123. package/dist/devices/airConditioner.d.ts +0 -98
  124. package/dist/devices/airConditioner.d.ts.map +0 -1
  125. package/dist/devices/airConditioner.js.map +0 -1
  126. package/dist/devices/batteryStorage.d.ts +0 -48
  127. package/dist/devices/batteryStorage.d.ts.map +0 -1
  128. package/dist/devices/batteryStorage.js.map +0 -1
  129. package/dist/devices/cooktop.d.ts +0 -61
  130. package/dist/devices/cooktop.d.ts.map +0 -1
  131. package/dist/devices/cooktop.js.map +0 -1
  132. package/dist/devices/dishwasher.d.ts +0 -71
  133. package/dist/devices/dishwasher.d.ts.map +0 -1
  134. package/dist/devices/dishwasher.js.map +0 -1
  135. package/dist/devices/evse.d.ts +0 -76
  136. package/dist/devices/evse.d.ts.map +0 -1
  137. package/dist/devices/evse.js.map +0 -1
  138. package/dist/devices/export.d.ts +0 -17
  139. package/dist/devices/export.d.ts.map +0 -1
  140. package/dist/devices/export.js.map +0 -1
  141. package/dist/devices/extractorHood.d.ts +0 -46
  142. package/dist/devices/extractorHood.d.ts.map +0 -1
  143. package/dist/devices/extractorHood.js.map +0 -1
  144. package/dist/devices/heatPump.d.ts +0 -47
  145. package/dist/devices/heatPump.d.ts.map +0 -1
  146. package/dist/devices/heatPump.js.map +0 -1
  147. package/dist/devices/laundryDryer.d.ts +0 -67
  148. package/dist/devices/laundryDryer.d.ts.map +0 -1
  149. package/dist/devices/laundryDryer.js.map +0 -1
  150. package/dist/devices/laundryWasher.d.ts +0 -81
  151. package/dist/devices/laundryWasher.d.ts.map +0 -1
  152. package/dist/devices/laundryWasher.js.map +0 -1
  153. package/dist/devices/microwaveOven.d.ts +0 -168
  154. package/dist/devices/microwaveOven.d.ts.map +0 -1
  155. package/dist/devices/microwaveOven.js.map +0 -1
  156. package/dist/devices/oven.d.ts +0 -105
  157. package/dist/devices/oven.d.ts.map +0 -1
  158. package/dist/devices/oven.js.map +0 -1
  159. package/dist/devices/refrigerator.d.ts +0 -118
  160. package/dist/devices/refrigerator.d.ts.map +0 -1
  161. package/dist/devices/refrigerator.js.map +0 -1
  162. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  163. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  164. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  165. package/dist/devices/solarPower.d.ts +0 -40
  166. package/dist/devices/solarPower.d.ts.map +0 -1
  167. package/dist/devices/solarPower.js.map +0 -1
  168. package/dist/devices/speaker.d.ts +0 -87
  169. package/dist/devices/speaker.d.ts.map +0 -1
  170. package/dist/devices/speaker.js.map +0 -1
  171. package/dist/devices/temperatureControl.d.ts +0 -166
  172. package/dist/devices/temperatureControl.d.ts.map +0 -1
  173. package/dist/devices/temperatureControl.js.map +0 -1
  174. package/dist/devices/waterHeater.d.ts +0 -111
  175. package/dist/devices/waterHeater.d.ts.map +0 -1
  176. package/dist/devices/waterHeater.js.map +0 -1
  177. package/dist/dgram/coap.d.ts +0 -205
  178. package/dist/dgram/coap.d.ts.map +0 -1
  179. package/dist/dgram/coap.js.map +0 -1
  180. package/dist/dgram/dgram.d.ts +0 -141
  181. package/dist/dgram/dgram.d.ts.map +0 -1
  182. package/dist/dgram/dgram.js.map +0 -1
  183. package/dist/dgram/mb_coap.d.ts +0 -24
  184. package/dist/dgram/mb_coap.d.ts.map +0 -1
  185. package/dist/dgram/mb_coap.js.map +0 -1
  186. package/dist/dgram/mb_mdns.d.ts +0 -24
  187. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  188. package/dist/dgram/mb_mdns.js.map +0 -1
  189. package/dist/dgram/mdns.d.ts +0 -290
  190. package/dist/dgram/mdns.d.ts.map +0 -1
  191. package/dist/dgram/mdns.js.map +0 -1
  192. package/dist/dgram/multicast.d.ts +0 -67
  193. package/dist/dgram/multicast.d.ts.map +0 -1
  194. package/dist/dgram/multicast.js.map +0 -1
  195. package/dist/dgram/unicast.d.ts +0 -56
  196. package/dist/dgram/unicast.d.ts.map +0 -1
  197. package/dist/dgram/unicast.js.map +0 -1
  198. package/dist/frontend.d.ts +0 -238
  199. package/dist/frontend.d.ts.map +0 -1
  200. package/dist/frontend.js.map +0 -1
  201. package/dist/frontendTypes.d.ts +0 -529
  202. package/dist/frontendTypes.d.ts.map +0 -1
  203. package/dist/frontendTypes.js.map +0 -1
  204. package/dist/helpers.d.ts +0 -48
  205. package/dist/helpers.d.ts.map +0 -1
  206. package/dist/helpers.js.map +0 -1
  207. package/dist/index.d.ts +0 -34
  208. package/dist/index.d.ts.map +0 -1
  209. package/dist/index.js.map +0 -1
  210. package/dist/jestutils/export.d.ts +0 -2
  211. package/dist/jestutils/export.d.ts.map +0 -1
  212. package/dist/jestutils/export.js.map +0 -1
  213. package/dist/jestutils/jestHelpers.d.ts +0 -303
  214. package/dist/jestutils/jestHelpers.d.ts.map +0 -1
  215. package/dist/jestutils/jestHelpers.js.map +0 -1
  216. package/dist/logger/export.d.ts +0 -2
  217. package/dist/logger/export.d.ts.map +0 -1
  218. package/dist/logger/export.js.map +0 -1
  219. package/dist/matter/behaviors.d.ts +0 -2
  220. package/dist/matter/behaviors.d.ts.map +0 -1
  221. package/dist/matter/behaviors.js.map +0 -1
  222. package/dist/matter/clusters.d.ts +0 -2
  223. package/dist/matter/clusters.d.ts.map +0 -1
  224. package/dist/matter/clusters.js.map +0 -1
  225. package/dist/matter/devices.d.ts +0 -2
  226. package/dist/matter/devices.d.ts.map +0 -1
  227. package/dist/matter/devices.js.map +0 -1
  228. package/dist/matter/endpoints.d.ts +0 -2
  229. package/dist/matter/endpoints.d.ts.map +0 -1
  230. package/dist/matter/endpoints.js.map +0 -1
  231. package/dist/matter/export.d.ts +0 -5
  232. package/dist/matter/export.d.ts.map +0 -1
  233. package/dist/matter/export.js.map +0 -1
  234. package/dist/matter/types.d.ts +0 -3
  235. package/dist/matter/types.d.ts.map +0 -1
  236. package/dist/matter/types.js.map +0 -1
  237. package/dist/matterNode.d.ts +0 -342
  238. package/dist/matterNode.d.ts.map +0 -1
  239. package/dist/matterNode.js.map +0 -1
  240. package/dist/matterbridge.d.ts +0 -473
  241. package/dist/matterbridge.d.ts.map +0 -1
  242. package/dist/matterbridge.js.map +0 -1
  243. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -41
  244. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  245. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  246. package/dist/matterbridgeBehaviors.d.ts +0 -2404
  247. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  248. package/dist/matterbridgeBehaviors.js.map +0 -1
  249. package/dist/matterbridgeDeviceTypes.d.ts +0 -698
  250. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  251. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  252. package/dist/matterbridgeDynamicPlatform.d.ts +0 -41
  253. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  254. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  255. package/dist/matterbridgeEndpoint.d.ts +0 -1507
  256. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  257. package/dist/matterbridgeEndpoint.js.map +0 -1
  258. package/dist/matterbridgeEndpointHelpers.d.ts +0 -787
  259. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  260. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  261. package/dist/matterbridgeEndpointTypes.d.ts +0 -166
  262. package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
  263. package/dist/matterbridgeEndpointTypes.js.map +0 -1
  264. package/dist/matterbridgePlatform.d.ts +0 -524
  265. package/dist/matterbridgePlatform.d.ts.map +0 -1
  266. package/dist/matterbridgePlatform.js.map +0 -1
  267. package/dist/matterbridgeTypes.d.ts +0 -251
  268. package/dist/matterbridgeTypes.d.ts.map +0 -1
  269. package/dist/matterbridgeTypes.js.map +0 -1
  270. package/dist/pluginManager.d.ts +0 -371
  271. package/dist/pluginManager.d.ts.map +0 -1
  272. package/dist/pluginManager.js.map +0 -1
  273. package/dist/shelly.d.ts +0 -174
  274. package/dist/shelly.d.ts.map +0 -1
  275. package/dist/shelly.js.map +0 -1
  276. package/dist/storage/export.d.ts +0 -2
  277. package/dist/storage/export.d.ts.map +0 -1
  278. package/dist/storage/export.js.map +0 -1
  279. package/dist/update.d.ts +0 -75
  280. package/dist/update.d.ts.map +0 -1
  281. package/dist/update.js.map +0 -1
  282. package/dist/utils/colorUtils.d.ts +0 -101
  283. package/dist/utils/colorUtils.d.ts.map +0 -1
  284. package/dist/utils/colorUtils.js.map +0 -1
  285. package/dist/utils/commandLine.d.ts +0 -66
  286. package/dist/utils/commandLine.d.ts.map +0 -1
  287. package/dist/utils/commandLine.js.map +0 -1
  288. package/dist/utils/copyDirectory.d.ts +0 -35
  289. package/dist/utils/copyDirectory.d.ts.map +0 -1
  290. package/dist/utils/copyDirectory.js.map +0 -1
  291. package/dist/utils/createDirectory.d.ts +0 -34
  292. package/dist/utils/createDirectory.d.ts.map +0 -1
  293. package/dist/utils/createDirectory.js.map +0 -1
  294. package/dist/utils/createZip.d.ts +0 -39
  295. package/dist/utils/createZip.d.ts.map +0 -1
  296. package/dist/utils/createZip.js.map +0 -1
  297. package/dist/utils/deepCopy.d.ts +0 -32
  298. package/dist/utils/deepCopy.d.ts.map +0 -1
  299. package/dist/utils/deepCopy.js.map +0 -1
  300. package/dist/utils/deepEqual.d.ts +0 -54
  301. package/dist/utils/deepEqual.d.ts.map +0 -1
  302. package/dist/utils/deepEqual.js.map +0 -1
  303. package/dist/utils/error.d.ts +0 -44
  304. package/dist/utils/error.d.ts.map +0 -1
  305. package/dist/utils/error.js.map +0 -1
  306. package/dist/utils/export.d.ts +0 -13
  307. package/dist/utils/export.d.ts.map +0 -1
  308. package/dist/utils/export.js.map +0 -1
  309. package/dist/utils/format.d.ts +0 -53
  310. package/dist/utils/format.d.ts.map +0 -1
  311. package/dist/utils/format.js.map +0 -1
  312. package/dist/utils/hex.d.ts +0 -89
  313. package/dist/utils/hex.d.ts.map +0 -1
  314. package/dist/utils/hex.js.map +0 -1
  315. package/dist/utils/inspector.d.ts +0 -87
  316. package/dist/utils/inspector.d.ts.map +0 -1
  317. package/dist/utils/inspector.js.map +0 -1
  318. package/dist/utils/isvalid.d.ts +0 -103
  319. package/dist/utils/isvalid.d.ts.map +0 -1
  320. package/dist/utils/isvalid.js.map +0 -1
  321. package/dist/utils/network.d.ts +0 -111
  322. package/dist/utils/network.d.ts.map +0 -1
  323. package/dist/utils/network.js.map +0 -1
  324. package/dist/utils/spawn.d.ts +0 -33
  325. package/dist/utils/spawn.d.ts.map +0 -1
  326. package/dist/utils/spawn.js.map +0 -1
  327. package/dist/utils/tracker.d.ts +0 -108
  328. package/dist/utils/tracker.d.ts.map +0 -1
  329. package/dist/utils/tracker.js.map +0 -1
  330. package/dist/utils/wait.d.ts +0 -54
  331. package/dist/utils/wait.d.ts.map +0 -1
  332. 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));
@@ -166,53 +140,23 @@ export class Frontend extends EventEmitter {
166
140
  this.port = port;
167
141
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
168
142
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
169
- // Initialize multer with the upload directory
170
143
  const multer = await import('multer');
171
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
144
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
172
145
  const upload = multer.default({ dest: uploadDir });
173
- // Create the express app that serves the frontend
174
146
  const express = await import('express');
175
147
  this.expressApp = express.default();
176
- // Inject logging/debug wrapper for route/middleware registration
177
- /*
178
- const methods = ['get', 'post', 'put', 'delete', 'use'];
179
- for (const method of methods) {
180
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
- const original = (this.expressApp as any)[method].bind(this.expressApp);
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
184
- try {
185
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
186
- return original(path, ...rest);
187
- } catch (err) {
188
- console.error(`[ERROR] Failed to register route: ${path}`);
189
- throw err;
190
- }
191
- };
192
- }
193
- */
194
- // Log all requests to the server for debugging
195
- /*
196
- this.expressApp.use((req, res, next) => {
197
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
198
- next();
199
- });
200
- */
201
- // Serve static files from 'frontend/build' directory
202
148
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
203
- // Create a WebSocket server and attach it to the http or https server
204
149
  this.log.debug(`Creating WebSocketServer...`);
205
150
  const ws = await import('ws');
206
151
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
207
152
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
208
153
  this.webSocketServer.on('connection', (ws, request) => {
209
154
  const clientIp = request.socket.remoteAddress;
210
- // Set the global logger callback for the WebSocketServer
211
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
212
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
213
- callbackLogLevel = "info" /* LogLevel.INFO */;
214
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
215
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
155
+ let callbackLogLevel = "notice";
156
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
157
+ callbackLogLevel = "info";
158
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
159
+ callbackLogLevel = "debug";
216
160
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
217
161
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
218
162
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -234,25 +178,16 @@ export class Frontend extends EventEmitter {
234
178
  }
235
179
  });
236
180
  ws.on('error', (error) => {
237
- // istanbul ignore next
238
181
  this.log.error(`WebSocket client error: ${error}`);
239
182
  });
240
183
  });
241
184
  this.webSocketServer.on('close', () => {
242
185
  this.log.debug(`WebSocketServer closed`);
243
186
  });
244
- /* With { noServer: true } it never fires
245
- this.webSocketServer.on('listening', () => {
246
- this.log.info(`The WebSocketServer is listening`);
247
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
248
- });
249
- */
250
- // istanbul ignore next
251
187
  this.webSocketServer.on('error', (ws, error) => {
252
188
  this.log.error(`WebSocketServer error: ${error}`);
253
189
  });
254
190
  if (!hasParameter('ssl')) {
255
- // Create an HTTP server and attach the express app
256
191
  const http = await import('node:http');
257
192
  try {
258
193
  this.log.debug(`Creating HTTP server...`);
@@ -263,7 +198,6 @@ export class Frontend extends EventEmitter {
263
198
  this.emit('server_error', error);
264
199
  return;
265
200
  }
266
- // Listen on the specified port
267
201
  if (hasParameter('ingress')) {
268
202
  this.httpServer.listen(this.port, '0.0.0.0', () => {
269
203
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -283,30 +217,24 @@ export class Frontend extends EventEmitter {
283
217
  }
284
218
  this.httpServer.on('upgrade', async (req, socket, head) => {
285
219
  try {
286
- // Only proceed for real WebSocket upgrades
287
- // istanbul ignore next cause is only a safety check
288
220
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
289
221
  this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
290
222
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
291
223
  return socket.destroy();
292
224
  }
293
- // Build a URL so we can read ?password=...
294
225
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
295
- // Validate WebSocket password
296
226
  const password = url.searchParams.get('password') ?? '';
297
227
  if (password !== this.storedPassword) {
298
228
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
299
229
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
300
230
  return socket.destroy();
301
231
  }
302
- // Complete the WebSocket handshake
303
232
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
304
233
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
305
234
  this.webSocketServer?.emit('connection', ws, req);
306
235
  });
307
236
  }
308
237
  catch (err) {
309
- /* istanbul ignore next: only triggered on unexpected internal error */
310
238
  {
311
239
  inspectError(this.log, 'WebSocket upgrade error:', err);
312
240
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -329,7 +257,6 @@ export class Frontend extends EventEmitter {
329
257
  });
330
258
  }
331
259
  else {
332
- // SSL is enabled, load the certificate and the private key
333
260
  let cert;
334
261
  let key;
335
262
  let ca;
@@ -339,7 +266,6 @@ export class Frontend extends EventEmitter {
339
266
  let httpsServerOptions = {};
340
267
  const fs = await import('node:fs');
341
268
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
342
- // Load the p12 certificate and the passphrase
343
269
  try {
344
270
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
345
271
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -351,7 +277,7 @@ export class Frontend extends EventEmitter {
351
277
  }
352
278
  try {
353
279
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
354
- passphrase = passphrase.trim(); // Ensure no extra characters
280
+ passphrase = passphrase.trim();
355
281
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
356
282
  }
357
283
  catch (error) {
@@ -362,7 +288,6 @@ export class Frontend extends EventEmitter {
362
288
  httpsServerOptions = { pfx, passphrase };
363
289
  }
364
290
  else {
365
- // 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.
366
291
  try {
367
292
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
368
293
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -392,10 +317,9 @@ export class Frontend extends EventEmitter {
392
317
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
393
318
  }
394
319
  if (hasParameter('mtls')) {
395
- httpsServerOptions.requestCert = true; // Request client certificate
396
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
320
+ httpsServerOptions.requestCert = true;
321
+ httpsServerOptions.rejectUnauthorized = true;
397
322
  }
398
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
399
323
  const https = await import('node:https');
400
324
  try {
401
325
  this.log.debug(`Creating HTTPS server...`);
@@ -406,7 +330,6 @@ export class Frontend extends EventEmitter {
406
330
  this.emit('server_error', error);
407
331
  return;
408
332
  }
409
- // Listen on the specified port
410
333
  if (hasParameter('ingress')) {
411
334
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
412
335
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -426,29 +349,23 @@ export class Frontend extends EventEmitter {
426
349
  }
427
350
  this.httpsServer.on('upgrade', async (req, socket, head) => {
428
351
  try {
429
- // Only proceed for real WebSocket upgrades
430
- // istanbul ignore next cause is only a safety check
431
352
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
432
353
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
433
354
  return socket.destroy();
434
355
  }
435
- // Build a URL so we can read ?password=...
436
356
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
437
- // Validate WebSocket password
438
357
  const password = url.searchParams.get('password') ?? '';
439
358
  if (password !== this.storedPassword) {
440
359
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
441
360
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
442
361
  return socket.destroy();
443
362
  }
444
- // Complete the WebSocket handshake
445
363
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
446
364
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
447
365
  this.webSocketServer?.emit('connection', ws, req);
448
366
  });
449
367
  }
450
368
  catch (err) {
451
- /* istanbul ignore next: only triggered on unexpected internal error */
452
369
  {
453
370
  inspectError(this.log, 'WebSocket upgrade error:', err);
454
371
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -470,7 +387,6 @@ export class Frontend extends EventEmitter {
470
387
  return;
471
388
  });
472
389
  }
473
- // Subscribe to cli events
474
390
  cliEmitter.removeAllListeners();
475
391
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
476
392
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -481,8 +397,6 @@ export class Frontend extends EventEmitter {
481
397
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
482
398
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
483
399
  });
484
- // Endpoint to validate login code
485
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
486
400
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
487
401
  const { password } = req.body;
488
402
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -495,20 +409,17 @@ export class Frontend extends EventEmitter {
495
409
  res.json({ valid: false });
496
410
  }
497
411
  });
498
- // Endpoint to provide health check for docker
499
412
  this.expressApp.get('/health', (req, res) => {
500
413
  this.log.debug('Express received /health');
501
414
  const healthStatus = {
502
- status: 'ok', // Indicate service is healthy
503
- uptime: process.uptime(), // Server uptime in seconds
504
- timestamp: new Date().toISOString(), // Current timestamp
415
+ status: 'ok',
416
+ uptime: process.uptime(),
417
+ timestamp: new Date().toISOString(),
505
418
  };
506
419
  res.status(200).json(healthStatus);
507
420
  });
508
- // Endpoint to provide memory usage details
509
421
  this.expressApp.get('/memory', async (req, res) => {
510
422
  this.log.debug('Express received /memory');
511
- // Memory usage from process
512
423
  const memoryUsageRaw = process.memoryUsage();
513
424
  const memoryUsage = {
514
425
  rss: formatBytes(memoryUsageRaw.rss),
@@ -517,13 +428,10 @@ export class Frontend extends EventEmitter {
517
428
  external: formatBytes(memoryUsageRaw.external),
518
429
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
519
430
  };
520
- // V8 heap statistics
521
431
  const { default: v8 } = await import('node:v8');
522
432
  const heapStatsRaw = v8.getHeapStatistics();
523
433
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
524
- // Format heapStats
525
434
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
526
- // Format heapSpaces
527
435
  const heapSpaces = heapSpacesRaw.map((space) => ({
528
436
  ...space,
529
437
  space_size: formatBytes(space.space_size),
@@ -542,22 +450,18 @@ export class Frontend extends EventEmitter {
542
450
  };
543
451
  res.status(200).json(memoryReport);
544
452
  });
545
- // Endpoint to provide settings
546
453
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
547
454
  this.log.debug('The frontend sent /api/settings');
548
455
  res.json(await this.getApiSettings());
549
456
  });
550
- // Endpoint to provide plugins
551
457
  this.expressApp.get('/api/plugins', async (req, res) => {
552
458
  this.log.debug('The frontend sent /api/plugins');
553
459
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
554
460
  });
555
- // Endpoint to provide devices
556
461
  this.expressApp.get('/api/devices', async (req, res) => {
557
462
  this.log.debug('The frontend sent /api/devices');
558
463
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
559
464
  });
560
- // Endpoint to view the matterbridge log
561
465
  this.expressApp.get('/api/view-mblog', async (req, res) => {
562
466
  this.log.debug('The frontend sent /api/view-mblog');
563
467
  try {
@@ -571,7 +475,6 @@ export class Frontend extends EventEmitter {
571
475
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
572
476
  }
573
477
  });
574
- // Endpoint to view the matter.js log
575
478
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
576
479
  this.log.debug('The frontend sent /api/view-mjlog');
577
480
  try {
@@ -585,7 +488,6 @@ export class Frontend extends EventEmitter {
585
488
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
586
489
  }
587
490
  });
588
- // Endpoint to view the diagnostic.log
589
491
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
590
492
  this.log.debug('The frontend sent /api/view-diagnostic');
591
493
  await this.generateDiagnostic();
@@ -596,13 +498,10 @@ export class Frontend extends EventEmitter {
596
498
  res.send(data.slice(29));
597
499
  }
598
500
  catch (error) {
599
- // istanbul ignore next
600
501
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
601
- // istanbul ignore next
602
502
  res.status(500).send('Error reading diagnostic log file.');
603
503
  }
604
504
  });
605
- // Endpoint to download the diagnostic.log
606
505
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
607
506
  this.log.debug(`The frontend sent /api/download-diagnostic`);
608
507
  await this.generateDiagnostic();
@@ -613,19 +512,16 @@ export class Frontend extends EventEmitter {
613
512
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
614
513
  }
615
514
  catch (error) {
616
- // istanbul ignore next
617
515
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
618
516
  }
619
517
  res.type('text/plain');
620
518
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
621
- /* istanbul ignore if */
622
519
  if (error) {
623
520
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
624
521
  res.status(500).send('Error downloading the diagnostic log file');
625
522
  }
626
523
  });
627
524
  });
628
- // Endpoint to view the history.html
629
525
  this.expressApp.get('/api/viewhistory', async (req, res) => {
630
526
  this.log.debug('The frontend sent /api/viewhistory');
631
527
  try {
@@ -639,7 +535,6 @@ export class Frontend extends EventEmitter {
639
535
  res.status(500).send('Error reading history file.');
640
536
  }
641
537
  });
642
- // Endpoint to download the history.html
643
538
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
644
539
  this.log.debug(`The frontend sent /api/downloadhistory`);
645
540
  try {
@@ -649,7 +544,6 @@ export class Frontend extends EventEmitter {
649
544
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
650
545
  res.type('text/plain');
651
546
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
652
- /* istanbul ignore if */
653
547
  if (error) {
654
548
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
655
549
  res.status(500).send('Error downloading history file');
@@ -661,7 +555,6 @@ export class Frontend extends EventEmitter {
661
555
  res.status(500).send('Error reading history file.');
662
556
  }
663
557
  });
664
- // Endpoint to view the shelly log
665
558
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
666
559
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
667
560
  try {
@@ -675,7 +568,6 @@ export class Frontend extends EventEmitter {
675
568
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
676
569
  }
677
570
  });
678
- // Endpoint to download the matterbridge log
679
571
  this.expressApp.get('/api/download-mblog', async (req, res) => {
680
572
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
681
573
  const fs = await import('node:fs');
@@ -690,14 +582,12 @@ export class Frontend extends EventEmitter {
690
582
  }
691
583
  res.type('text/plain');
692
584
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
693
- /* istanbul ignore if */
694
585
  if (error) {
695
586
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
696
587
  res.status(500).send('Error downloading the matterbridge log file');
697
588
  }
698
589
  });
699
590
  });
700
- // Endpoint to download the matter log
701
591
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
702
592
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
703
593
  const fs = await import('node:fs');
@@ -712,14 +602,12 @@ export class Frontend extends EventEmitter {
712
602
  }
713
603
  res.type('text/plain');
714
604
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
715
- /* istanbul ignore if */
716
605
  if (error) {
717
606
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
718
607
  res.status(500).send('Error downloading the matter log file');
719
608
  }
720
609
  });
721
610
  });
722
- // Endpoint to download the shelly log
723
611
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
724
612
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
725
613
  const fs = await import('node:fs');
@@ -734,91 +622,75 @@ export class Frontend extends EventEmitter {
734
622
  }
735
623
  res.type('text/plain');
736
624
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
737
- /* istanbul ignore if */
738
625
  if (error) {
739
626
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
740
627
  res.status(500).send('Error downloading Shelly system log file');
741
628
  }
742
629
  });
743
630
  });
744
- // Endpoint to download the matterbridge storage directory
745
631
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
746
632
  this.log.debug('The frontend sent /api/download-mbstorage');
747
633
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
748
634
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
749
- /* istanbul ignore if */
750
635
  if (error) {
751
636
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
752
637
  res.status(500).send('Error downloading the matterbridge storage file');
753
638
  }
754
639
  });
755
640
  });
756
- // Endpoint to download the matter storage file
757
641
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
758
642
  this.log.debug('The frontend sent /api/download-mjstorage');
759
643
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
760
644
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
761
- /* istanbul ignore if */
762
645
  if (error) {
763
646
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
764
647
  res.status(500).send('Error downloading the matter storage zip file');
765
648
  }
766
649
  });
767
650
  });
768
- // Endpoint to download the matterbridge plugin directory
769
651
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
770
652
  this.log.debug('The frontend sent /api/download-pluginstorage');
771
653
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
772
654
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
773
- /* istanbul ignore if */
774
655
  if (error) {
775
656
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
776
657
  res.status(500).send('Error downloading the matterbridge plugin storage file');
777
658
  }
778
659
  });
779
660
  });
780
- // Endpoint to download the matterbridge plugin config files
781
661
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
782
662
  this.log.debug('The frontend sent /api/download-pluginconfig');
783
663
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
784
664
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
785
- /* istanbul ignore if */
786
665
  if (error) {
787
666
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
788
667
  res.status(500).send('Error downloading the matterbridge plugin config file');
789
668
  }
790
669
  });
791
670
  });
792
- // Endpoint to download the matterbridge backup (created with the backup command)
793
671
  this.expressApp.get('/api/download-backup', async (req, res) => {
794
672
  this.log.debug('The frontend sent /api/download-backup');
795
673
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
796
- /* istanbul ignore if */
797
674
  if (error) {
798
675
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
799
676
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
800
677
  }
801
678
  });
802
679
  });
803
- // Endpoint to upload a package
804
680
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
805
681
  const { filename } = req.body;
806
682
  const file = req.file;
807
- /* istanbul ignore if */
808
683
  if (!file || !filename) {
809
684
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
810
685
  res.status(400).send('Invalid request: file and filename are required');
811
686
  return;
812
687
  }
813
688
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
814
- // Define the path where the plugin file will be saved
815
689
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
816
690
  try {
817
- // Move the uploaded file to the specified path
818
691
  const fs = await import('node:fs');
819
692
  await fs.promises.rename(file.path, filePath);
820
693
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
821
- // Install the plugin package
822
694
  if (filename.endsWith('.tgz')) {
823
695
  const { spawnCommand } = await import('./utils/spawn.js');
824
696
  if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
@@ -846,7 +718,6 @@ export class Frontend extends EventEmitter {
846
718
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
847
719
  }
848
720
  });
849
- // Fallback for routing (must be the last route)
850
721
  this.expressApp.use((req, res) => {
851
722
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
852
723
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -857,16 +728,13 @@ export class Frontend extends EventEmitter {
857
728
  async stop() {
858
729
  this.log.debug('Stopping the frontend...');
859
730
  const ws = await import('ws');
860
- // Remove listeners from the express app
861
731
  if (this.expressApp) {
862
732
  this.expressApp.removeAllListeners();
863
733
  this.expressApp = undefined;
864
734
  this.log.debug('Frontend app closed successfully');
865
735
  }
866
- // Close the WebSocket server
867
736
  if (this.webSocketServer) {
868
737
  this.log.debug('Closing WebSocket server...');
869
- // Close all active connections
870
738
  this.webSocketServer.clients.forEach((client) => {
871
739
  if (client.readyState === ws.WebSocket.OPEN) {
872
740
  client.close();
@@ -875,7 +743,6 @@ export class Frontend extends EventEmitter {
875
743
  await withTimeout(new Promise((resolve) => {
876
744
  this.webSocketServer?.close((error) => {
877
745
  if (error) {
878
- // istanbul ignore next
879
746
  this.log.error(`Error closing WebSocket server: ${error}`);
880
747
  }
881
748
  else {
@@ -888,27 +755,8 @@ export class Frontend extends EventEmitter {
888
755
  this.webSocketServer.removeAllListeners();
889
756
  this.webSocketServer = undefined;
890
757
  }
891
- // Close the http server
892
758
  if (this.httpServer) {
893
759
  this.log.debug('Closing http server...');
894
- /*
895
- await withTimeout(
896
- new Promise<void>((resolve) => {
897
- this.httpServer?.close((error) => {
898
- if (error) {
899
- // istanbul ignore next
900
- this.log.error(`Error closing http server: ${error}`);
901
- } else {
902
- this.log.debug('Http server closed successfully');
903
- this.emit('server_stopped');
904
- }
905
- resolve();
906
- });
907
- }),
908
- 5000,
909
- false,
910
- );
911
- */
912
760
  this.httpServer.close();
913
761
  this.log.debug('Http server closed successfully');
914
762
  this.listening = false;
@@ -917,27 +765,8 @@ export class Frontend extends EventEmitter {
917
765
  this.httpServer = undefined;
918
766
  this.log.debug('Frontend http server closed successfully');
919
767
  }
920
- // Close the https server
921
768
  if (this.httpsServer) {
922
769
  this.log.debug('Closing https server...');
923
- /*
924
- await withTimeout(
925
- new Promise<void>((resolve) => {
926
- this.httpsServer?.close((error) => {
927
- if (error) {
928
- // istanbul ignore next
929
- this.log.error(`Error closing https server: ${error}`);
930
- } else {
931
- this.log.debug('Https server closed successfully');
932
- this.emit('server_stopped');
933
- }
934
- resolve();
935
- });
936
- }),
937
- 5000,
938
- false,
939
- );
940
- */
941
770
  this.httpsServer.close();
942
771
  this.log.debug('Https server closed successfully');
943
772
  this.listening = false;
@@ -948,13 +777,7 @@ export class Frontend extends EventEmitter {
948
777
  }
949
778
  this.log.debug('Frontend stopped successfully');
950
779
  }
951
- /**
952
- * Retrieves the api settings data.
953
- *
954
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
955
- */
956
780
  async getApiSettings() {
957
- // Update the variable system information properties
958
781
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
959
782
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
960
783
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -964,7 +787,6 @@ export class Frontend extends EventEmitter {
964
787
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
965
788
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
966
789
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
967
- // Create the matterbridge information
968
790
  const info = {
969
791
  homeDirectory: this.matterbridge.homeDirectory,
970
792
  rootDirectory: this.matterbridge.rootDirectory,
@@ -1000,15 +822,9 @@ export class Frontend extends EventEmitter {
1000
822
  };
1001
823
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
1002
824
  }
1003
- /**
1004
- * Retrieves the reachable attribute.
1005
- *
1006
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
1007
- * @returns {boolean} The reachable attribute.
1008
- */
1009
825
  getReachability(device) {
1010
826
  if (this.matterbridge.hasCleanupStarted)
1011
- return false; // Skip if cleanup has started
827
+ return false;
1012
828
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1013
829
  return false;
1014
830
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -1019,15 +835,9 @@ export class Frontend extends EventEmitter {
1019
835
  return true;
1020
836
  return false;
1021
837
  }
1022
- /**
1023
- * Retrieves the power source attribute.
1024
- *
1025
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1026
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1027
- */
1028
838
  getPowerSource(endpoint) {
1029
839
  if (this.matterbridge.hasCleanupStarted)
1030
- return; // Skip if cleanup has started
840
+ return;
1031
841
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1032
842
  return undefined;
1033
843
  const powerSource = (device) => {
@@ -1042,25 +852,16 @@ export class Frontend extends EventEmitter {
1042
852
  }
1043
853
  return;
1044
854
  };
1045
- // Root endpoint
1046
855
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1047
856
  return powerSource(endpoint);
1048
- // Child endpoints
1049
857
  for (const child of endpoint.getChildEndpoints()) {
1050
858
  if (child.hasClusterServer(PowerSource.Cluster.id))
1051
859
  return powerSource(child);
1052
860
  }
1053
861
  }
1054
- /**
1055
- * Retrieves the cluster text description from a given device.
1056
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1057
- *
1058
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1059
- * @returns {string} The attributes description of the cluster servers in the device.
1060
- */
1061
862
  getClusterTextFromDevice(device) {
1062
863
  if (this.matterbridge.hasCleanupStarted)
1063
- return ''; // Skip if cleanup has started
864
+ return '';
1064
865
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1065
866
  return '';
1066
867
  const getUserLabel = (device) => {
@@ -1070,7 +871,6 @@ export class Frontend extends EventEmitter {
1070
871
  if (composed)
1071
872
  return 'Composed: ' + composed.value;
1072
873
  }
1073
- // istanbul ignore next cause is not reachable
1074
874
  return '';
1075
875
  };
1076
876
  const getFixedLabel = (device) => {
@@ -1080,13 +880,11 @@ export class Frontend extends EventEmitter {
1080
880
  if (composed)
1081
881
  return 'Composed: ' + composed.value;
1082
882
  }
1083
- // istanbul ignore next cause is not reacheable
1084
883
  return '';
1085
884
  };
1086
885
  let attributes = '';
1087
886
  let supportedModes = [];
1088
887
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1089
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1090
888
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1091
889
  return;
1092
890
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1176,17 +974,11 @@ export class Frontend extends EventEmitter {
1176
974
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1177
975
  attributes += `${getUserLabel(device)} `;
1178
976
  });
1179
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1180
977
  return attributes.trimStart().trimEnd();
1181
978
  }
1182
- /**
1183
- * Retrieves the registered plugins sanitized for res.json().
1184
- *
1185
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1186
- */
1187
979
  getPlugins() {
1188
980
  if (this.matterbridge.hasCleanupStarted)
1189
- return []; // Skip if cleanup has started
981
+ return [];
1190
982
  const plugins = [];
1191
983
  for (const plugin of this.matterbridge.plugins.array()) {
1192
984
  plugins.push({
@@ -1214,27 +1006,18 @@ export class Frontend extends EventEmitter {
1214
1006
  schemaJson: plugin.schemaJson,
1215
1007
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1216
1008
  hasBlackList: plugin.configJson?.blackList !== undefined,
1217
- // Childbridge mode specific data
1218
1009
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1219
1010
  });
1220
1011
  }
1221
1012
  return plugins;
1222
1013
  }
1223
- /**
1224
- * Retrieves the devices from Matterbridge.
1225
- *
1226
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1227
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1228
- */
1229
1014
  getDevices(pluginName) {
1230
1015
  if (this.matterbridge.hasCleanupStarted)
1231
- return []; // Skip if cleanup has started
1016
+ return [];
1232
1017
  const devices = [];
1233
1018
  for (const device of this.matterbridge.devices.array()) {
1234
- // Filter by pluginName if provided
1235
1019
  if (pluginName && pluginName !== device.plugin)
1236
1020
  continue;
1237
- // Check if the device has the required properties
1238
1021
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1239
1022
  continue;
1240
1023
  devices.push({
@@ -1254,39 +1037,24 @@ export class Frontend extends EventEmitter {
1254
1037
  }
1255
1038
  return devices;
1256
1039
  }
1257
- /**
1258
- * Retrieves the clusters from a given plugin and endpoint number.
1259
- *
1260
- * Response for /api/clusters
1261
- *
1262
- * @param {string} pluginName - The name of the plugin.
1263
- * @param {number} endpointNumber - The endpoint number.
1264
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1265
- */
1266
1040
  getClusters(pluginName, endpointNumber) {
1267
1041
  if (this.matterbridge.hasCleanupStarted)
1268
- return; // Skip if cleanup has started
1042
+ return;
1269
1043
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1270
1044
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1271
1045
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1272
1046
  return;
1273
1047
  }
1274
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1275
- // Get the device types from the main endpoint
1276
1048
  const deviceTypes = [];
1277
1049
  const clusters = [];
1278
1050
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1279
1051
  deviceTypes.push(d.deviceType);
1280
1052
  });
1281
- // Get the clusters from the main endpoint
1282
1053
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1283
1054
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1284
1055
  return;
1285
1056
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1286
1057
  return;
1287
- // console.log(
1288
- // `${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}`,
1289
- // );
1290
1058
  clusters.push({
1291
1059
  endpoint: endpoint.number.toString(),
1292
1060
  number: endpoint.number,
@@ -1300,19 +1068,12 @@ export class Frontend extends EventEmitter {
1300
1068
  attributeLocalValue: attributeValue,
1301
1069
  });
1302
1070
  });
1303
- // Get the child endpoints
1304
1071
  const childEndpoints = endpoint.getChildEndpoints();
1305
- // if (childEndpoints.length === 0) {
1306
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1307
- // }
1308
1072
  childEndpoints.forEach((childEndpoint) => {
1309
- // istanbul ignore if cause is not reachable: should never happen but ...
1310
1073
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1311
1074
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1312
1075
  return;
1313
1076
  }
1314
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1315
- // Get the device types of the child endpoint
1316
1077
  const deviceTypes = [];
1317
1078
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1318
1079
  deviceTypes.push(d.deviceType);
@@ -1322,9 +1083,6 @@ export class Frontend extends EventEmitter {
1322
1083
  return;
1323
1084
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1324
1085
  return;
1325
- // console.log(
1326
- // `${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}`,
1327
- // );
1328
1086
  clusters.push({
1329
1087
  endpoint: childEndpoint.number.toString(),
1330
1088
  number: childEndpoint.number,
@@ -1344,7 +1102,6 @@ export class Frontend extends EventEmitter {
1344
1102
  async generateDiagnostic() {
1345
1103
  this.log.debug('Generating diagnostic...');
1346
1104
  const serverNodes = [];
1347
- // istanbul ignore else
1348
1105
  if (this.matterbridge.bridgeMode === 'bridge') {
1349
1106
  if (this.matterbridge.serverNode)
1350
1107
  serverNodes.push(this.matterbridge.serverNode);
@@ -1355,7 +1112,6 @@ export class Frontend extends EventEmitter {
1355
1112
  serverNodes.push(plugin.serverNode);
1356
1113
  }
1357
1114
  }
1358
- // istanbul ignore next
1359
1115
  for (const device of this.matterbridge.devices.array()) {
1360
1116
  if (device.serverNode)
1361
1117
  serverNodes.push(device.serverNode);
@@ -1379,15 +1135,8 @@ export class Frontend extends EventEmitter {
1379
1135
  values: [...serverNodes],
1380
1136
  })));
1381
1137
  delete Logger.destinations.diagnostic;
1382
- await wait(500); // Wait for the log to be written
1138
+ await wait(500);
1383
1139
  }
1384
- /**
1385
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1386
- *
1387
- * @param {WebSocket} client - The websocket client that sent the message.
1388
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1389
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1390
- */
1391
1140
  async wsMessageHandler(client, message) {
1392
1141
  let data;
1393
1142
  const sendResponse = (data) => {
@@ -1405,13 +1154,12 @@ export class Frontend extends EventEmitter {
1405
1154
  client.send(JSON.stringify(data));
1406
1155
  }
1407
1156
  else {
1408
- // istanbul ignore next cause is only a safety check
1409
1157
  this.log.error('Cannot send api response, client not connected');
1410
1158
  }
1411
1159
  };
1412
1160
  try {
1413
1161
  data = JSON.parse(message.toString());
1414
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1162
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1415
1163
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1416
1164
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1417
1165
  return;
@@ -1468,22 +1216,7 @@ export class Frontend extends EventEmitter {
1468
1216
  }
1469
1217
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1470
1218
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1471
- data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
1472
- /*
1473
- const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
1474
- if (plugin) {
1475
- this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1476
- await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
1477
- this.wssSendRestartRequired();
1478
- this.wssSendRefreshRequired('plugins');
1479
- this.wssSendRefreshRequired('devices');
1480
- this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1481
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1482
- } else {
1483
- this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1484
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1485
- }
1486
- */
1219
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1487
1220
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1488
1221
  if (plugin) {
1489
1222
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1497,7 +1230,6 @@ export class Frontend extends EventEmitter {
1497
1230
  return;
1498
1231
  })
1499
1232
  .catch((_error) => {
1500
- //
1501
1233
  });
1502
1234
  }
1503
1235
  else {
@@ -1512,10 +1244,6 @@ export class Frontend extends EventEmitter {
1512
1244
  }
1513
1245
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1514
1246
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1515
- /*
1516
- 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);
1517
- await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
1518
- */
1519
1247
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1520
1248
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1521
1249
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1551,7 +1279,6 @@ export class Frontend extends EventEmitter {
1551
1279
  return;
1552
1280
  })
1553
1281
  .catch((_error) => {
1554
- //
1555
1282
  });
1556
1283
  }
1557
1284
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1577,7 +1304,6 @@ export class Frontend extends EventEmitter {
1577
1304
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1578
1305
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1579
1306
  if (plugin.serverNode) {
1580
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1581
1307
  await this.matterbridge.stopServerNode(plugin.serverNode);
1582
1308
  plugin.serverNode = undefined;
1583
1309
  }
@@ -1587,20 +1313,18 @@ export class Frontend extends EventEmitter {
1587
1313
  this.matterbridge.devices.remove(device);
1588
1314
  }
1589
1315
  }
1590
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1591
1316
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1592
1317
  await this.matterbridge.createDynamicPlugin(plugin);
1593
1318
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1594
- plugin.restartRequired = false; // Reset plugin restartRequired
1319
+ plugin.restartRequired = false;
1595
1320
  let needRestart = 0;
1596
1321
  for (const plugin of this.matterbridge.plugins) {
1597
1322
  if (plugin.restartRequired)
1598
1323
  needRestart++;
1599
1324
  }
1600
1325
  if (needRestart === 0) {
1601
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1326
+ this.wssSendRestartNotRequired(true);
1602
1327
  }
1603
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1604
1328
  if (plugin.serverNode)
1605
1329
  await this.matterbridge.startServerNode(plugin.serverNode);
1606
1330
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1745,9 +1469,6 @@ export class Frontend extends EventEmitter {
1745
1469
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
1746
1470
  }
1747
1471
  if (data.params.advertise) {
1748
- // TODO: matter.js 0.16.0
1749
- // await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
1750
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1751
1472
  const advertiser = serverNode.env.get(DeviceAdvertiser);
1752
1473
  if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
1753
1474
  await advertiser.advertise(true);
@@ -1873,22 +1594,22 @@ export class Frontend extends EventEmitter {
1873
1594
  if (isValidString(data.params.value, 4)) {
1874
1595
  this.log.debug('Matterbridge logger level:', data.params.value);
1875
1596
  if (data.params.value === 'Debug') {
1876
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1597
+ await this.matterbridge.setLogLevel("debug");
1877
1598
  }
1878
1599
  else if (data.params.value === 'Info') {
1879
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1600
+ await this.matterbridge.setLogLevel("info");
1880
1601
  }
1881
1602
  else if (data.params.value === 'Notice') {
1882
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1603
+ await this.matterbridge.setLogLevel("notice");
1883
1604
  }
1884
1605
  else if (data.params.value === 'Warn') {
1885
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1606
+ await this.matterbridge.setLogLevel("warn");
1886
1607
  }
1887
1608
  else if (data.params.value === 'Error') {
1888
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1609
+ await this.matterbridge.setLogLevel("error");
1889
1610
  }
1890
1611
  else if (data.params.value === 'Fatal') {
1891
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1612
+ await this.matterbridge.setLogLevel("fatal");
1892
1613
  }
1893
1614
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1894
1615
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1899,7 +1620,6 @@ export class Frontend extends EventEmitter {
1899
1620
  this.log.debug('Matterbridge file log:', data.params.value);
1900
1621
  this.matterbridge.fileLogger = data.params.value;
1901
1622
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1902
- // Create the file logger for matterbridge
1903
1623
  if (data.params.value)
1904
1624
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1905
1625
  else
@@ -1929,12 +1649,11 @@ export class Frontend extends EventEmitter {
1929
1649
  Logger.level = MatterLogLevel.FATAL;
1930
1650
  }
1931
1651
  this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
1932
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1933
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1934
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1935
- callbackLogLevel = "info" /* LogLevel.INFO */;
1936
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1937
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1652
+ let callbackLogLevel = "notice";
1653
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1654
+ callbackLogLevel = "info";
1655
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1656
+ callbackLogLevel = "debug";
1938
1657
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1939
1658
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1940
1659
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -1986,7 +1705,6 @@ export class Frontend extends EventEmitter {
1986
1705
  }
1987
1706
  break;
1988
1707
  case 'setmatterport':
1989
- // eslint-disable-next-line no-case-declarations
1990
1708
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1991
1709
  if (isValidNumber(port, 5540, 5600)) {
1992
1710
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -2006,7 +1724,6 @@ export class Frontend extends EventEmitter {
2006
1724
  }
2007
1725
  break;
2008
1726
  case 'setmatterdiscriminator':
2009
- // eslint-disable-next-line no-case-declarations
2010
1727
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2011
1728
  if (isValidNumber(discriminator, 0, 4095)) {
2012
1729
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -2026,7 +1743,6 @@ export class Frontend extends EventEmitter {
2026
1743
  }
2027
1744
  break;
2028
1745
  case 'setmatterpasscode':
2029
- // eslint-disable-next-line no-case-declarations
2030
1746
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2031
1747
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
2032
1748
  this.matterbridge.passcode = passcode;
@@ -2072,19 +1788,15 @@ export class Frontend extends EventEmitter {
2072
1788
  return;
2073
1789
  }
2074
1790
  const config = plugin.configJson;
2075
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2076
1791
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2077
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2078
1792
  if (select === 'serial')
2079
1793
  this.log.info(`Selected device serial ${data.params.serial}`);
2080
1794
  if (select === 'name')
2081
1795
  this.log.info(`Selected device name ${data.params.name}`);
2082
1796
  if (config && select && (select === 'serial' || select === 'name')) {
2083
- // Remove postfix from the serial if it exists
2084
1797
  if (config.postfix) {
2085
1798
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2086
1799
  }
2087
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2088
1800
  if (isValidArray(config.whiteList, 1)) {
2089
1801
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2090
1802
  config.whiteList.push(data.params.serial);
@@ -2093,7 +1805,6 @@ export class Frontend extends EventEmitter {
2093
1805
  config.whiteList.push(data.params.name);
2094
1806
  }
2095
1807
  }
2096
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2097
1808
  if (isValidArray(config.blackList, 1)) {
2098
1809
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2099
1810
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2121,9 +1832,7 @@ export class Frontend extends EventEmitter {
2121
1832
  return;
2122
1833
  }
2123
1834
  const config = plugin.configJson;
2124
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2125
1835
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2126
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2127
1836
  if (select === 'serial')
2128
1837
  this.log.info(`Unselected device serial ${data.params.serial}`);
2129
1838
  if (select === 'name')
@@ -2132,7 +1841,6 @@ export class Frontend extends EventEmitter {
2132
1841
  if (config.postfix) {
2133
1842
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2134
1843
  }
2135
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2136
1844
  if (isValidArray(config.whiteList, 1)) {
2137
1845
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2138
1846
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2141,7 +1849,6 @@ export class Frontend extends EventEmitter {
2141
1849
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2142
1850
  }
2143
1851
  }
2144
- // Add the serial to the blackList
2145
1852
  if (isValidArray(config.blackList)) {
2146
1853
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2147
1854
  config.blackList.push(data.params.serial);
@@ -2164,7 +1871,6 @@ export class Frontend extends EventEmitter {
2164
1871
  }
2165
1872
  }
2166
1873
  else {
2167
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2168
1874
  const localData = data;
2169
1875
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2170
1876
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2174,46 +1880,23 @@ export class Frontend extends EventEmitter {
2174
1880
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2175
1881
  }
2176
1882
  }
2177
- /**
2178
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2179
- *
2180
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2181
- * @param {string} time - The time string of the message
2182
- * @param {string} name - The logger name of the message
2183
- * @param {string} message - The content of the message.
2184
- *
2185
- * @remarks
2186
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2187
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2188
- * The function sends the message to all connected clients.
2189
- */
2190
1883
  wssSendLogMessage(level, time, name, message) {
2191
1884
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2192
1885
  return;
2193
1886
  if (!level || !time || !name || !message)
2194
1887
  return;
2195
- // Remove ANSI escape codes from the message
2196
- // eslint-disable-next-line no-control-regex
2197
1888
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2198
- // Remove leading asterisks from the message
2199
1889
  message = message.replace(/^\*+/, '');
2200
- // Replace all occurrences of \t and \n
2201
1890
  message = message.replace(/[\t\n]/g, '');
2202
- // Remove non-printable characters
2203
- // eslint-disable-next-line no-control-regex
2204
1891
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2205
- // Replace all occurrences of \" with "
2206
1892
  message = message.replace(/\\"/g, '"');
2207
- // Define the maximum allowed length for continuous characters without a space
2208
1893
  const maxContinuousLength = 100;
2209
1894
  const keepStartLength = 20;
2210
1895
  const keepEndLength = 20;
2211
- // Split the message into words
2212
1896
  if (level !== 'spawn') {
2213
1897
  message = message
2214
1898
  .split(' ')
2215
1899
  .map((word) => {
2216
- // If the word length exceeds the max continuous length, insert spaces and truncate
2217
1900
  if (word.length > maxContinuousLength) {
2218
1901
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2219
1902
  }
@@ -2221,34 +1904,14 @@ export class Frontend extends EventEmitter {
2221
1904
  })
2222
1905
  .join(' ');
2223
1906
  }
2224
- // Send the message to all connected clients
2225
1907
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2226
1908
  }
2227
- /**
2228
- * Sends a need to refresh WebSocket message to all connected clients.
2229
- *
2230
- * @param {string} changed - The changed value.
2231
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2232
- * possible values for changed:
2233
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2234
- * - 'plugins'
2235
- * - 'devices'
2236
- * - 'matter' with param 'matter' (QRDiv component)
2237
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2238
- */
2239
1909
  wssSendRefreshRequired(changed, params) {
2240
1910
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2241
1911
  return;
2242
1912
  this.log.debug('Sending a refresh required message to all connected clients');
2243
- // Send the message to all connected clients
2244
1913
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2245
1914
  }
2246
- /**
2247
- * Sends a need to restart WebSocket message to all connected clients.
2248
- *
2249
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2250
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2251
- */
2252
1915
  wssSendRestartRequired(snackbar = true, fixed = false) {
2253
1916
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2254
1917
  return;
@@ -2257,14 +1920,8 @@ export class Frontend extends EventEmitter {
2257
1920
  this.matterbridge.fixedRestartRequired = fixed;
2258
1921
  if (snackbar === true)
2259
1922
  this.wssSendSnackbarMessage(`Restart required`, 0);
2260
- // Send the message to all connected clients
2261
1923
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2262
1924
  }
2263
- /**
2264
- * Sends a no need to restart WebSocket message to all connected clients.
2265
- *
2266
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2267
- */
2268
1925
  wssSendRestartNotRequired(snackbar = true) {
2269
1926
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2270
1927
  return;
@@ -2272,133 +1929,57 @@ export class Frontend extends EventEmitter {
2272
1929
  this.matterbridge.restartRequired = false;
2273
1930
  if (snackbar === true)
2274
1931
  this.wssSendCloseSnackbarMessage(`Restart required`);
2275
- // Send the message to all connected clients
2276
1932
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2277
1933
  }
2278
- /**
2279
- * Sends a need to update WebSocket message to all connected clients.
2280
- *
2281
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2282
- */
2283
1934
  wssSendUpdateRequired(devVersion = false) {
2284
1935
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2285
1936
  return;
2286
1937
  this.log.debug('Sending an update required message to all connected clients');
2287
1938
  this.matterbridge.updateRequired = true;
2288
- // Send the message to all connected clients
2289
1939
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2290
1940
  }
2291
- /**
2292
- * Sends a cpu update message to all connected clients.
2293
- *
2294
- * @param {number} cpuUsage - The CPU usage percentage to send.
2295
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2296
- */
2297
1941
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2298
1942
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2299
1943
  return;
2300
1944
  if (hasParameter('debug'))
2301
1945
  this.log.debug('Sending a cpu update message to all connected clients');
2302
- // Send the message to all connected clients
2303
1946
  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 } });
2304
1947
  }
2305
- /**
2306
- * Sends a memory update message to all connected clients.
2307
- *
2308
- * @param {string} totalMemory - The total memory in bytes.
2309
- * @param {string} freeMemory - The free memory in bytes.
2310
- * @param {string} rss - The resident set size in bytes.
2311
- * @param {string} heapTotal - The total heap memory in bytes.
2312
- * @param {string} heapUsed - The used heap memory in bytes.
2313
- * @param {string} external - The external memory in bytes.
2314
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2315
- */
2316
1948
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2317
1949
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2318
1950
  return;
2319
1951
  if (hasParameter('debug'))
2320
1952
  this.log.debug('Sending a memory update message to all connected clients');
2321
- // Send the message to all connected clients
2322
1953
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2323
1954
  }
2324
- /**
2325
- * Sends an uptime update message to all connected clients.
2326
- *
2327
- * @param {string} systemUptime - The system uptime in a human-readable format.
2328
- * @param {string} processUptime - The process uptime in a human-readable format.
2329
- */
2330
1955
  wssSendUptimeUpdate(systemUptime, processUptime) {
2331
1956
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2332
1957
  return;
2333
1958
  if (hasParameter('debug'))
2334
1959
  this.log.debug('Sending a uptime update message to all connected clients');
2335
- // Send the message to all connected clients
2336
1960
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2337
1961
  }
2338
- /**
2339
- * Sends an open snackbar message to all connected clients.
2340
- *
2341
- * @param {string} message - The message to send.
2342
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2343
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2344
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2345
- *
2346
- * @remarks
2347
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2348
- */
2349
1962
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2350
1963
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2351
1964
  return;
2352
1965
  this.log.debug('Sending a snackbar message to all connected clients');
2353
- // Send the message to all connected clients
2354
1966
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2355
1967
  }
2356
- /**
2357
- * Sends a close snackbar message to all connected clients.
2358
- * It will close the snackbar message with the same message and timeout = 0.
2359
- *
2360
- * @param {string} message - The message to send.
2361
- */
2362
1968
  wssSendCloseSnackbarMessage(message) {
2363
1969
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2364
1970
  return;
2365
1971
  this.log.debug('Sending a close snackbar message to all connected clients');
2366
- // Send the message to all connected clients
2367
1972
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2368
1973
  }
2369
- /**
2370
- * Sends an attribute update message to all connected WebSocket clients.
2371
- *
2372
- * @param {string | undefined} plugin - The name of the plugin.
2373
- * @param {string | undefined} serialNumber - The serial number of the device.
2374
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2375
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2376
- * @param {string} id - The endpoint id where the attribute belongs.
2377
- * @param {string} cluster - The cluster name where the attribute belongs.
2378
- * @param {string} attribute - The name of the attribute that changed.
2379
- * @param {number | string | boolean} value - The new value of the attribute.
2380
- *
2381
- * @remarks
2382
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2383
- * with the updated attribute information.
2384
- */
2385
1974
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2386
1975
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2387
1976
  return;
2388
1977
  this.log.debug('Sending an attribute update message to all connected clients');
2389
- // Send the message to all connected clients
2390
1978
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2391
1979
  }
2392
- /**
2393
- * Sends a message to all connected clients.
2394
- * This is an helper function to send a broadcast message to all connected clients.
2395
- *
2396
- * @param {WsMessageBroadcast} msg - The message to send.
2397
- */
2398
1980
  wssBroadcastMessage(msg) {
2399
1981
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2400
1982
  return;
2401
- // Send the message to all connected clients
2402
1983
  const stringifiedMsg = JSON.stringify(msg);
2403
1984
  if (msg.method !== 'log')
2404
1985
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -2409,4 +1990,3 @@ export class Frontend extends EventEmitter {
2409
1990
  });
2410
1991
  }
2411
1992
  }
2412
- //# sourceMappingURL=frontend.js.map