matterbridge 3.4.4 → 3.4.5-dev-20251222-5853b56

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 (325) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +17 -2
  3. package/dist/broadcastServer.js +0 -117
  4. package/dist/broadcastServerTypes.js +0 -24
  5. package/dist/cli.js +1 -97
  6. package/dist/cliEmitter.js +0 -37
  7. package/dist/cliHistory.js +0 -38
  8. package/dist/clusters/export.js +0 -2
  9. package/dist/deviceManager.js +1 -113
  10. package/dist/devices/airConditioner.js +0 -57
  11. package/dist/devices/batteryStorage.js +1 -48
  12. package/dist/devices/cooktop.js +0 -56
  13. package/dist/devices/dishwasher.js +0 -57
  14. package/dist/devices/evse.js +10 -74
  15. package/dist/devices/export.js +0 -5
  16. package/dist/devices/extractorHood.js +0 -43
  17. package/dist/devices/heatPump.js +2 -50
  18. package/dist/devices/laundryDryer.js +3 -62
  19. package/dist/devices/laundryWasher.js +4 -70
  20. package/dist/devices/microwaveOven.js +5 -88
  21. package/dist/devices/oven.js +0 -85
  22. package/dist/devices/refrigerator.js +0 -102
  23. package/dist/devices/roboticVacuumCleaner.js +9 -100
  24. package/dist/devices/solarPower.js +0 -38
  25. package/dist/devices/speaker.js +0 -84
  26. package/dist/devices/temperatureControl.js +3 -24
  27. package/dist/devices/waterHeater.js +2 -82
  28. package/dist/dgram/coap.js +13 -126
  29. package/dist/dgram/dgram.js +2 -114
  30. package/dist/dgram/mb_coap.js +3 -41
  31. package/dist/dgram/mb_mdns.js +15 -80
  32. package/dist/dgram/mdns.js +137 -299
  33. package/dist/dgram/multicast.js +1 -62
  34. package/dist/dgram/unicast.js +0 -54
  35. package/dist/frontend.js +38 -485
  36. package/dist/frontendTypes.js +0 -45
  37. package/dist/helpers.js +0 -53
  38. package/dist/index.js +0 -25
  39. package/dist/jestutils/export.js +0 -1
  40. package/dist/jestutils/jestHelpers.js +14 -371
  41. package/dist/logger/export.js +0 -1
  42. package/dist/matter/behaviors.js +0 -2
  43. package/dist/matter/clusters.js +0 -2
  44. package/dist/matter/devices.js +0 -2
  45. package/dist/matter/endpoints.js +0 -2
  46. package/dist/matter/export.js +0 -3
  47. package/dist/matter/types.js +0 -3
  48. package/dist/matterNode.js +8 -369
  49. package/dist/matterbridge.js +46 -824
  50. package/dist/matterbridgeAccessoryPlatform.js +0 -38
  51. package/dist/matterbridgeBehaviors.js +5 -68
  52. package/dist/matterbridgeDeviceTypes.js +14 -635
  53. package/dist/matterbridgeDynamicPlatform.js +0 -38
  54. package/dist/matterbridgeEndpoint.js +53 -1457
  55. package/dist/matterbridgeEndpointHelpers.js +20 -483
  56. package/dist/matterbridgeEndpointTypes.js +0 -25
  57. package/dist/matterbridgePlatform.js +1 -451
  58. package/dist/matterbridgeTypes.js +0 -26
  59. package/dist/pluginManager.js +5 -341
  60. package/dist/shelly.js +7 -178
  61. package/dist/storage/export.js +0 -1
  62. package/dist/update.js +1 -93
  63. package/dist/utils/colorUtils.js +2 -97
  64. package/dist/utils/commandLine.js +0 -60
  65. package/dist/utils/copyDirectory.js +0 -37
  66. package/dist/utils/createDirectory.js +0 -33
  67. package/dist/utils/createZip.js +2 -47
  68. package/dist/utils/deepCopy.js +0 -39
  69. package/dist/utils/deepEqual.js +1 -72
  70. package/dist/utils/error.js +0 -42
  71. package/dist/utils/export.js +0 -1
  72. package/dist/utils/format.js +0 -49
  73. package/dist/utils/hex.js +0 -124
  74. package/dist/utils/inspector.js +1 -69
  75. package/dist/utils/isvalid.js +0 -101
  76. package/dist/utils/network.js +5 -96
  77. package/dist/utils/spawn.js +1 -71
  78. package/dist/utils/tracker.js +1 -64
  79. package/dist/utils/wait.js +8 -60
  80. package/dist/workerGlobalPrefix.js +5 -37
  81. package/dist/workerTypes.js +0 -24
  82. package/dist/workers.js +4 -68
  83. package/npm-shrinkwrap.json +2 -2
  84. package/package.json +1 -2
  85. package/dist/broadcastServer.d.ts +0 -144
  86. package/dist/broadcastServer.d.ts.map +0 -1
  87. package/dist/broadcastServer.js.map +0 -1
  88. package/dist/broadcastServerTypes.d.ts +0 -841
  89. package/dist/broadcastServerTypes.d.ts.map +0 -1
  90. package/dist/broadcastServerTypes.js.map +0 -1
  91. package/dist/cli.d.ts +0 -30
  92. package/dist/cli.d.ts.map +0 -1
  93. package/dist/cli.js.map +0 -1
  94. package/dist/cliEmitter.d.ts +0 -50
  95. package/dist/cliEmitter.d.ts.map +0 -1
  96. package/dist/cliEmitter.js.map +0 -1
  97. package/dist/cliHistory.d.ts +0 -48
  98. package/dist/cliHistory.d.ts.map +0 -1
  99. package/dist/cliHistory.js.map +0 -1
  100. package/dist/clusters/export.d.ts +0 -2
  101. package/dist/clusters/export.d.ts.map +0 -1
  102. package/dist/clusters/export.js.map +0 -1
  103. package/dist/deviceManager.d.ts +0 -135
  104. package/dist/deviceManager.d.ts.map +0 -1
  105. package/dist/deviceManager.js.map +0 -1
  106. package/dist/devices/airConditioner.d.ts +0 -98
  107. package/dist/devices/airConditioner.d.ts.map +0 -1
  108. package/dist/devices/airConditioner.js.map +0 -1
  109. package/dist/devices/batteryStorage.d.ts +0 -48
  110. package/dist/devices/batteryStorage.d.ts.map +0 -1
  111. package/dist/devices/batteryStorage.js.map +0 -1
  112. package/dist/devices/cooktop.d.ts +0 -61
  113. package/dist/devices/cooktop.d.ts.map +0 -1
  114. package/dist/devices/cooktop.js.map +0 -1
  115. package/dist/devices/dishwasher.d.ts +0 -71
  116. package/dist/devices/dishwasher.d.ts.map +0 -1
  117. package/dist/devices/dishwasher.js.map +0 -1
  118. package/dist/devices/evse.d.ts +0 -76
  119. package/dist/devices/evse.d.ts.map +0 -1
  120. package/dist/devices/evse.js.map +0 -1
  121. package/dist/devices/export.d.ts +0 -17
  122. package/dist/devices/export.d.ts.map +0 -1
  123. package/dist/devices/export.js.map +0 -1
  124. package/dist/devices/extractorHood.d.ts +0 -46
  125. package/dist/devices/extractorHood.d.ts.map +0 -1
  126. package/dist/devices/extractorHood.js.map +0 -1
  127. package/dist/devices/heatPump.d.ts +0 -47
  128. package/dist/devices/heatPump.d.ts.map +0 -1
  129. package/dist/devices/heatPump.js.map +0 -1
  130. package/dist/devices/laundryDryer.d.ts +0 -67
  131. package/dist/devices/laundryDryer.d.ts.map +0 -1
  132. package/dist/devices/laundryDryer.js.map +0 -1
  133. package/dist/devices/laundryWasher.d.ts +0 -81
  134. package/dist/devices/laundryWasher.d.ts.map +0 -1
  135. package/dist/devices/laundryWasher.js.map +0 -1
  136. package/dist/devices/microwaveOven.d.ts +0 -168
  137. package/dist/devices/microwaveOven.d.ts.map +0 -1
  138. package/dist/devices/microwaveOven.js.map +0 -1
  139. package/dist/devices/oven.d.ts +0 -105
  140. package/dist/devices/oven.d.ts.map +0 -1
  141. package/dist/devices/oven.js.map +0 -1
  142. package/dist/devices/refrigerator.d.ts +0 -118
  143. package/dist/devices/refrigerator.d.ts.map +0 -1
  144. package/dist/devices/refrigerator.js.map +0 -1
  145. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  146. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  147. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  148. package/dist/devices/solarPower.d.ts +0 -40
  149. package/dist/devices/solarPower.d.ts.map +0 -1
  150. package/dist/devices/solarPower.js.map +0 -1
  151. package/dist/devices/speaker.d.ts +0 -87
  152. package/dist/devices/speaker.d.ts.map +0 -1
  153. package/dist/devices/speaker.js.map +0 -1
  154. package/dist/devices/temperatureControl.d.ts +0 -166
  155. package/dist/devices/temperatureControl.d.ts.map +0 -1
  156. package/dist/devices/temperatureControl.js.map +0 -1
  157. package/dist/devices/waterHeater.d.ts +0 -111
  158. package/dist/devices/waterHeater.d.ts.map +0 -1
  159. package/dist/devices/waterHeater.js.map +0 -1
  160. package/dist/dgram/coap.d.ts +0 -205
  161. package/dist/dgram/coap.d.ts.map +0 -1
  162. package/dist/dgram/coap.js.map +0 -1
  163. package/dist/dgram/dgram.d.ts +0 -141
  164. package/dist/dgram/dgram.d.ts.map +0 -1
  165. package/dist/dgram/dgram.js.map +0 -1
  166. package/dist/dgram/mb_coap.d.ts +0 -24
  167. package/dist/dgram/mb_coap.d.ts.map +0 -1
  168. package/dist/dgram/mb_coap.js.map +0 -1
  169. package/dist/dgram/mb_mdns.d.ts +0 -24
  170. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  171. package/dist/dgram/mb_mdns.js.map +0 -1
  172. package/dist/dgram/mdns.d.ts +0 -290
  173. package/dist/dgram/mdns.d.ts.map +0 -1
  174. package/dist/dgram/mdns.js.map +0 -1
  175. package/dist/dgram/multicast.d.ts +0 -67
  176. package/dist/dgram/multicast.d.ts.map +0 -1
  177. package/dist/dgram/multicast.js.map +0 -1
  178. package/dist/dgram/unicast.d.ts +0 -56
  179. package/dist/dgram/unicast.d.ts.map +0 -1
  180. package/dist/dgram/unicast.js.map +0 -1
  181. package/dist/frontend.d.ts +0 -245
  182. package/dist/frontend.d.ts.map +0 -1
  183. package/dist/frontend.js.map +0 -1
  184. package/dist/frontendTypes.d.ts +0 -529
  185. package/dist/frontendTypes.d.ts.map +0 -1
  186. package/dist/frontendTypes.js.map +0 -1
  187. package/dist/helpers.d.ts +0 -48
  188. package/dist/helpers.d.ts.map +0 -1
  189. package/dist/helpers.js.map +0 -1
  190. package/dist/index.d.ts +0 -34
  191. package/dist/index.d.ts.map +0 -1
  192. package/dist/index.js.map +0 -1
  193. package/dist/jestutils/export.d.ts +0 -2
  194. package/dist/jestutils/export.d.ts.map +0 -1
  195. package/dist/jestutils/export.js.map +0 -1
  196. package/dist/jestutils/jestHelpers.d.ts +0 -345
  197. package/dist/jestutils/jestHelpers.d.ts.map +0 -1
  198. package/dist/jestutils/jestHelpers.js.map +0 -1
  199. package/dist/logger/export.d.ts +0 -2
  200. package/dist/logger/export.d.ts.map +0 -1
  201. package/dist/logger/export.js.map +0 -1
  202. package/dist/matter/behaviors.d.ts +0 -2
  203. package/dist/matter/behaviors.d.ts.map +0 -1
  204. package/dist/matter/behaviors.js.map +0 -1
  205. package/dist/matter/clusters.d.ts +0 -2
  206. package/dist/matter/clusters.d.ts.map +0 -1
  207. package/dist/matter/clusters.js.map +0 -1
  208. package/dist/matter/devices.d.ts +0 -2
  209. package/dist/matter/devices.d.ts.map +0 -1
  210. package/dist/matter/devices.js.map +0 -1
  211. package/dist/matter/endpoints.d.ts +0 -2
  212. package/dist/matter/endpoints.d.ts.map +0 -1
  213. package/dist/matter/endpoints.js.map +0 -1
  214. package/dist/matter/export.d.ts +0 -5
  215. package/dist/matter/export.d.ts.map +0 -1
  216. package/dist/matter/export.js.map +0 -1
  217. package/dist/matter/types.d.ts +0 -3
  218. package/dist/matter/types.d.ts.map +0 -1
  219. package/dist/matter/types.js.map +0 -1
  220. package/dist/matterNode.d.ts +0 -342
  221. package/dist/matterNode.d.ts.map +0 -1
  222. package/dist/matterNode.js.map +0 -1
  223. package/dist/matterbridge.d.ts +0 -505
  224. package/dist/matterbridge.d.ts.map +0 -1
  225. package/dist/matterbridge.js.map +0 -1
  226. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -41
  227. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  228. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  229. package/dist/matterbridgeBehaviors.d.ts +0 -2404
  230. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  231. package/dist/matterbridgeBehaviors.js.map +0 -1
  232. package/dist/matterbridgeDeviceTypes.d.ts +0 -698
  233. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  234. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  235. package/dist/matterbridgeDynamicPlatform.d.ts +0 -41
  236. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  237. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  238. package/dist/matterbridgeEndpoint.d.ts +0 -1507
  239. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  240. package/dist/matterbridgeEndpoint.js.map +0 -1
  241. package/dist/matterbridgeEndpointHelpers.d.ts +0 -787
  242. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  243. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  244. package/dist/matterbridgeEndpointTypes.d.ts +0 -166
  245. package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
  246. package/dist/matterbridgeEndpointTypes.js.map +0 -1
  247. package/dist/matterbridgePlatform.d.ts +0 -539
  248. package/dist/matterbridgePlatform.d.ts.map +0 -1
  249. package/dist/matterbridgePlatform.js.map +0 -1
  250. package/dist/matterbridgeTypes.d.ts +0 -252
  251. package/dist/matterbridgeTypes.d.ts.map +0 -1
  252. package/dist/matterbridgeTypes.js.map +0 -1
  253. package/dist/pluginManager.d.ts +0 -372
  254. package/dist/pluginManager.d.ts.map +0 -1
  255. package/dist/pluginManager.js.map +0 -1
  256. package/dist/shelly.d.ts +0 -181
  257. package/dist/shelly.d.ts.map +0 -1
  258. package/dist/shelly.js.map +0 -1
  259. package/dist/storage/export.d.ts +0 -2
  260. package/dist/storage/export.d.ts.map +0 -1
  261. package/dist/storage/export.js.map +0 -1
  262. package/dist/update.d.ts +0 -84
  263. package/dist/update.d.ts.map +0 -1
  264. package/dist/update.js.map +0 -1
  265. package/dist/utils/colorUtils.d.ts +0 -101
  266. package/dist/utils/colorUtils.d.ts.map +0 -1
  267. package/dist/utils/colorUtils.js.map +0 -1
  268. package/dist/utils/commandLine.d.ts +0 -66
  269. package/dist/utils/commandLine.d.ts.map +0 -1
  270. package/dist/utils/commandLine.js.map +0 -1
  271. package/dist/utils/copyDirectory.d.ts +0 -35
  272. package/dist/utils/copyDirectory.d.ts.map +0 -1
  273. package/dist/utils/copyDirectory.js.map +0 -1
  274. package/dist/utils/createDirectory.d.ts +0 -34
  275. package/dist/utils/createDirectory.d.ts.map +0 -1
  276. package/dist/utils/createDirectory.js.map +0 -1
  277. package/dist/utils/createZip.d.ts +0 -39
  278. package/dist/utils/createZip.d.ts.map +0 -1
  279. package/dist/utils/createZip.js.map +0 -1
  280. package/dist/utils/deepCopy.d.ts +0 -32
  281. package/dist/utils/deepCopy.d.ts.map +0 -1
  282. package/dist/utils/deepCopy.js.map +0 -1
  283. package/dist/utils/deepEqual.d.ts +0 -54
  284. package/dist/utils/deepEqual.d.ts.map +0 -1
  285. package/dist/utils/deepEqual.js.map +0 -1
  286. package/dist/utils/error.d.ts +0 -45
  287. package/dist/utils/error.d.ts.map +0 -1
  288. package/dist/utils/error.js.map +0 -1
  289. package/dist/utils/export.d.ts +0 -13
  290. package/dist/utils/export.d.ts.map +0 -1
  291. package/dist/utils/export.js.map +0 -1
  292. package/dist/utils/format.d.ts +0 -53
  293. package/dist/utils/format.d.ts.map +0 -1
  294. package/dist/utils/format.js.map +0 -1
  295. package/dist/utils/hex.d.ts +0 -89
  296. package/dist/utils/hex.d.ts.map +0 -1
  297. package/dist/utils/hex.js.map +0 -1
  298. package/dist/utils/inspector.d.ts +0 -87
  299. package/dist/utils/inspector.d.ts.map +0 -1
  300. package/dist/utils/inspector.js.map +0 -1
  301. package/dist/utils/isvalid.d.ts +0 -103
  302. package/dist/utils/isvalid.d.ts.map +0 -1
  303. package/dist/utils/isvalid.js.map +0 -1
  304. package/dist/utils/network.d.ts +0 -111
  305. package/dist/utils/network.d.ts.map +0 -1
  306. package/dist/utils/network.js.map +0 -1
  307. package/dist/utils/spawn.d.ts +0 -33
  308. package/dist/utils/spawn.d.ts.map +0 -1
  309. package/dist/utils/spawn.js.map +0 -1
  310. package/dist/utils/tracker.d.ts +0 -108
  311. package/dist/utils/tracker.d.ts.map +0 -1
  312. package/dist/utils/tracker.js.map +0 -1
  313. package/dist/utils/wait.d.ts +0 -54
  314. package/dist/utils/wait.d.ts.map +0 -1
  315. package/dist/utils/wait.js.map +0 -1
  316. package/dist/workerGlobalPrefix.d.ts +0 -25
  317. package/dist/workerGlobalPrefix.d.ts.map +0 -1
  318. package/dist/workerGlobalPrefix.js.map +0 -1
  319. package/dist/workerTypes.d.ts +0 -52
  320. package/dist/workerTypes.d.ts.map +0 -1
  321. package/dist/workerTypes.js.map +0 -1
  322. package/dist/workers.d.ts +0 -69
  323. package/dist/workers.d.ts.map +0 -1
  324. package/dist/workers.js.map +0 -1
  325. package/marked.ps1 +0 -25
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.3
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 */ /* istanbul ignore next */
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));
@@ -74,7 +48,6 @@ export class Frontend extends EventEmitter {
74
48
  }
75
49
  async msgHandler(msg) {
76
50
  if (this.server.isWorkerRequest(msg)) {
77
- // istanbul ignore else
78
51
  if (this.verbose)
79
52
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
80
53
  switch (msg.type) {
@@ -126,13 +99,11 @@ export class Frontend extends EventEmitter {
126
99
  this.server.respond({ ...msg, result: { success: true } });
127
100
  break;
128
101
  default:
129
- // istanbul ignore next
130
102
  if (this.verbose)
131
103
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
132
104
  }
133
105
  }
134
106
  if (this.server.isWorkerResponse(msg) && msg.result) {
135
- // istanbul ignore next
136
107
  if (this.verbose)
137
108
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
138
109
  switch (msg.type) {
@@ -168,55 +139,23 @@ export class Frontend extends EventEmitter {
168
139
  this.port = port;
169
140
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
170
141
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
171
- // Initialize multer with the upload directory
172
142
  const multer = await import('multer');
173
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
143
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
174
144
  const upload = multer.default({ dest: uploadDir });
175
- // Create the express app that serves the frontend
176
145
  const express = await import('express');
177
146
  this.expressApp = express.default();
178
- // Inject logging/debug wrapper for route/middleware registration
179
- /*
180
- const methods = ['get', 'post', 'put', 'delete', 'use'];
181
- for (const method of methods) {
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
- const original = (this.expressApp as any)[method].bind(this.expressApp);
184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
186
- try {
187
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
188
- return original(path, ...rest);
189
- } catch (err) {
190
- console.error(`[ERROR] Failed to register route: ${path}`);
191
- throw err;
192
- }
193
- };
194
- }
195
- */
196
- // Log all requests to the server for debugging
197
- /*
198
- this.expressApp.use((req, res, next) => {
199
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
200
- next();
201
- });
202
- */
203
- // Serve static files from 'frontend/build' directory
204
147
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
205
- // Create a WebSocket server and attach it to the http or https server
206
148
  this.log.debug(`Creating WebSocketServer...`);
207
149
  const ws = await import('ws');
208
150
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
209
151
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
210
152
  this.webSocketServer.on('connection', (ws, request) => {
211
153
  const clientIp = request.socket.remoteAddress;
212
- // Set the global logger callback for the WebSocketServer
213
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
214
- // istanbul ignore else
215
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
216
- callbackLogLevel = "info" /* LogLevel.INFO */;
217
- // istanbul ignore else
218
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
219
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
154
+ let callbackLogLevel = "notice";
155
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
156
+ callbackLogLevel = "info";
157
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
158
+ callbackLogLevel = "debug";
220
159
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
221
160
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
222
161
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -232,33 +171,22 @@ export class Frontend extends EventEmitter {
232
171
  });
233
172
  ws.on('close', () => {
234
173
  this.log.info('WebSocket client disconnected');
235
- // istanbul ignore else
236
174
  if (this.webSocketServer?.clients.size === 0) {
237
175
  AnsiLogger.setGlobalCallback(undefined);
238
176
  this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
239
177
  }
240
178
  });
241
- // istanbul ignore next
242
179
  ws.on('error', (error) => {
243
- // istanbul ignore next
244
180
  this.log.error(`WebSocket client error: ${error}`);
245
181
  });
246
182
  });
247
183
  this.webSocketServer.on('close', () => {
248
184
  this.log.debug(`WebSocketServer closed`);
249
185
  });
250
- /* With { noServer: true } it never fires
251
- this.webSocketServer.on('listening', () => {
252
- this.log.info(`The WebSocketServer is listening`);
253
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
254
- });
255
- */
256
- // istanbul ignore next
257
186
  this.webSocketServer.on('error', (ws, error) => {
258
187
  this.log.error(`WebSocketServer error: ${error}`);
259
188
  });
260
189
  if (!hasParameter('ssl')) {
261
- // Create an HTTP server and attach the express app
262
190
  const http = await import('node:http');
263
191
  try {
264
192
  this.log.debug(`Creating HTTP server...`);
@@ -269,7 +197,6 @@ export class Frontend extends EventEmitter {
269
197
  this.emit('server_error', error);
270
198
  return;
271
199
  }
272
- // Listen on the specified port
273
200
  if (hasParameter('ingress')) {
274
201
  this.httpServer.listen(this.port, '0.0.0.0', () => {
275
202
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -279,10 +206,8 @@ export class Frontend extends EventEmitter {
279
206
  }
280
207
  else {
281
208
  this.httpServer.listen(this.port, () => {
282
- // istanbul ignore else
283
209
  if (this.matterbridge.systemInformation.ipv4Address !== '')
284
210
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
285
- // istanbul ignore else
286
211
  if (this.matterbridge.systemInformation.ipv6Address !== '')
287
212
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
288
213
  this.listening = true;
@@ -291,30 +216,24 @@ export class Frontend extends EventEmitter {
291
216
  }
292
217
  this.httpServer.on('upgrade', async (req, socket, head) => {
293
218
  try {
294
- // Only proceed for real WebSocket upgrades
295
- // istanbul ignore next cause is only a safety check
296
219
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
297
220
  this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
298
221
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
299
222
  return socket.destroy();
300
223
  }
301
- // Build a URL so we can read ?password=...
302
224
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
303
- // Validate WebSocket password
304
225
  const password = url.searchParams.get('password') ?? '';
305
226
  if (password !== this.storedPassword) {
306
227
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
307
228
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
308
229
  return socket.destroy();
309
230
  }
310
- // Complete the WebSocket handshake
311
231
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
312
232
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
313
233
  this.webSocketServer?.emit('connection', ws, req);
314
234
  });
315
235
  }
316
236
  catch (err) {
317
- /* istanbul ignore next: only triggered on unexpected internal error */
318
237
  {
319
238
  inspectError(this.log, 'WebSocket upgrade error:', err);
320
239
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -337,7 +256,6 @@ export class Frontend extends EventEmitter {
337
256
  });
338
257
  }
339
258
  else {
340
- // SSL is enabled, load the certificate and the private key
341
259
  let cert;
342
260
  let key;
343
261
  let ca;
@@ -347,7 +265,6 @@ export class Frontend extends EventEmitter {
347
265
  let httpsServerOptions = {};
348
266
  const fs = await import('node:fs');
349
267
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
350
- // Load the p12 certificate and the passphrase
351
268
  try {
352
269
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
353
270
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -359,7 +276,7 @@ export class Frontend extends EventEmitter {
359
276
  }
360
277
  try {
361
278
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
362
- passphrase = passphrase.trim(); // Ensure no extra characters
279
+ passphrase = passphrase.trim();
363
280
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
364
281
  }
365
282
  catch (error) {
@@ -370,7 +287,6 @@ export class Frontend extends EventEmitter {
370
287
  httpsServerOptions = { pfx, passphrase };
371
288
  }
372
289
  else {
373
- // 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.
374
290
  try {
375
291
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
376
292
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -400,10 +316,9 @@ export class Frontend extends EventEmitter {
400
316
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
401
317
  }
402
318
  if (hasParameter('mtls')) {
403
- httpsServerOptions.requestCert = true; // Request client certificate
404
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
319
+ httpsServerOptions.requestCert = true;
320
+ httpsServerOptions.rejectUnauthorized = true;
405
321
  }
406
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
407
322
  const https = await import('node:https');
408
323
  try {
409
324
  this.log.debug(`Creating HTTPS server...`);
@@ -414,7 +329,6 @@ export class Frontend extends EventEmitter {
414
329
  this.emit('server_error', error);
415
330
  return;
416
331
  }
417
- // Listen on the specified port
418
332
  if (hasParameter('ingress')) {
419
333
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
420
334
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -424,10 +338,8 @@ export class Frontend extends EventEmitter {
424
338
  }
425
339
  else {
426
340
  this.httpsServer.listen(this.port, () => {
427
- // istanbul ignore else
428
341
  if (this.matterbridge.systemInformation.ipv4Address !== '')
429
342
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
430
- // istanbul ignore else
431
343
  if (this.matterbridge.systemInformation.ipv6Address !== '')
432
344
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
433
345
  this.listening = true;
@@ -436,29 +348,23 @@ export class Frontend extends EventEmitter {
436
348
  }
437
349
  this.httpsServer.on('upgrade', async (req, socket, head) => {
438
350
  try {
439
- // Only proceed for real WebSocket upgrades
440
- // istanbul ignore next cause is only a safety check
441
351
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
442
352
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
443
353
  return socket.destroy();
444
354
  }
445
- // Build a URL so we can read ?password=...
446
355
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
447
- // Validate WebSocket password
448
356
  const password = url.searchParams.get('password') ?? '';
449
357
  if (password !== this.storedPassword) {
450
358
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
451
359
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
452
360
  return socket.destroy();
453
361
  }
454
- // Complete the WebSocket handshake
455
362
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
456
363
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
457
364
  this.webSocketServer?.emit('connection', ws, req);
458
365
  });
459
366
  }
460
367
  catch (err) {
461
- /* istanbul ignore next: only triggered on unexpected internal error */
462
368
  {
463
369
  inspectError(this.log, 'WebSocket upgrade error:', err);
464
370
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -480,7 +386,6 @@ export class Frontend extends EventEmitter {
480
386
  return;
481
387
  });
482
388
  }
483
- // Subscribe to cli events
484
389
  cliEmitter.removeAllListeners();
485
390
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
486
391
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -491,8 +396,6 @@ export class Frontend extends EventEmitter {
491
396
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
492
397
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
493
398
  });
494
- // Endpoint to validate login code
495
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
496
399
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
497
400
  const { password } = req.body;
498
401
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -505,20 +408,17 @@ export class Frontend extends EventEmitter {
505
408
  res.json({ valid: false });
506
409
  }
507
410
  });
508
- // Endpoint to provide health check for docker
509
411
  this.expressApp.get('/health', (req, res) => {
510
412
  this.log.debug('Express received /health');
511
413
  const healthStatus = {
512
- status: 'ok', // Indicate service is healthy
513
- uptime: process.uptime(), // Server uptime in seconds
514
- timestamp: new Date().toISOString(), // Current timestamp
414
+ status: 'ok',
415
+ uptime: process.uptime(),
416
+ timestamp: new Date().toISOString(),
515
417
  };
516
418
  res.status(200).json(healthStatus);
517
419
  });
518
- // Endpoint to provide memory usage details
519
420
  this.expressApp.get('/memory', async (req, res) => {
520
421
  this.log.debug('Express received /memory');
521
- // Memory usage from process
522
422
  const memoryUsageRaw = process.memoryUsage();
523
423
  const memoryUsage = {
524
424
  rss: formatBytes(memoryUsageRaw.rss),
@@ -527,13 +427,10 @@ export class Frontend extends EventEmitter {
527
427
  external: formatBytes(memoryUsageRaw.external),
528
428
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
529
429
  };
530
- // V8 heap statistics
531
430
  const { default: v8 } = await import('node:v8');
532
431
  const heapStatsRaw = v8.getHeapStatistics();
533
432
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
534
- // Format heapStats
535
433
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
536
- // Format heapSpaces
537
434
  const heapSpaces = heapSpacesRaw.map((space) => ({
538
435
  ...space,
539
436
  space_size: formatBytes(space.space_size),
@@ -552,22 +449,18 @@ export class Frontend extends EventEmitter {
552
449
  };
553
450
  res.status(200).json(memoryReport);
554
451
  });
555
- // Endpoint to provide settings
556
452
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
557
453
  this.log.debug('The frontend sent /api/settings');
558
454
  res.json(await this.getApiSettings());
559
455
  });
560
- // Endpoint to provide plugins
561
456
  this.expressApp.get('/api/plugins', async (req, res) => {
562
457
  this.log.debug('The frontend sent /api/plugins');
563
458
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
564
459
  });
565
- // Endpoint to provide devices
566
460
  this.expressApp.get('/api/devices', async (req, res) => {
567
461
  this.log.debug('The frontend sent /api/devices');
568
462
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
569
463
  });
570
- // Endpoint to view the matterbridge log
571
464
  this.expressApp.get('/api/view-mblog', async (req, res) => {
572
465
  this.log.debug('The frontend sent /api/view-mblog');
573
466
  try {
@@ -581,7 +474,6 @@ export class Frontend extends EventEmitter {
581
474
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
582
475
  }
583
476
  });
584
- // Endpoint to view the matter.js log
585
477
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
586
478
  this.log.debug('The frontend sent /api/view-mjlog');
587
479
  try {
@@ -595,7 +487,6 @@ export class Frontend extends EventEmitter {
595
487
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
596
488
  }
597
489
  });
598
- // Endpoint to view the diagnostic.log
599
490
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
600
491
  this.log.debug('The frontend sent /api/view-diagnostic');
601
492
  await this.generateDiagnostic();
@@ -606,13 +497,10 @@ export class Frontend extends EventEmitter {
606
497
  res.send(data.slice(29));
607
498
  }
608
499
  catch (error) {
609
- // istanbul ignore next
610
500
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
611
- // istanbul ignore next
612
501
  res.status(500).send('Error reading diagnostic log file.');
613
502
  }
614
503
  });
615
- // Endpoint to download the diagnostic.log
616
504
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
617
505
  this.log.debug(`The frontend sent /api/download-diagnostic`);
618
506
  await this.generateDiagnostic();
@@ -623,19 +511,16 @@ export class Frontend extends EventEmitter {
623
511
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
624
512
  }
625
513
  catch (error) {
626
- // istanbul ignore next
627
514
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
628
515
  }
629
516
  res.type('text/plain');
630
517
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
631
- /* istanbul ignore if */
632
518
  if (error) {
633
519
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
634
520
  res.status(500).send('Error downloading the diagnostic log file');
635
521
  }
636
522
  });
637
523
  });
638
- // Endpoint to view the history.html
639
524
  this.expressApp.get('/api/viewhistory', async (req, res) => {
640
525
  this.log.debug('The frontend sent /api/viewhistory');
641
526
  try {
@@ -649,7 +534,6 @@ export class Frontend extends EventEmitter {
649
534
  res.status(500).send('Error reading history file.');
650
535
  }
651
536
  });
652
- // Endpoint to download the history.html
653
537
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
654
538
  this.log.debug(`The frontend sent /api/downloadhistory`);
655
539
  try {
@@ -659,7 +543,6 @@ export class Frontend extends EventEmitter {
659
543
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
660
544
  res.type('text/plain');
661
545
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
662
- /* istanbul ignore if */
663
546
  if (error) {
664
547
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
665
548
  res.status(500).send('Error downloading history file');
@@ -671,7 +554,6 @@ export class Frontend extends EventEmitter {
671
554
  res.status(500).send('Error reading history file.');
672
555
  }
673
556
  });
674
- // Endpoint to view the shelly log
675
557
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
676
558
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
677
559
  try {
@@ -685,7 +567,6 @@ export class Frontend extends EventEmitter {
685
567
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
686
568
  }
687
569
  });
688
- // Endpoint to download the matterbridge log
689
570
  this.expressApp.get('/api/download-mblog', async (req, res) => {
690
571
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
691
572
  const fs = await import('node:fs');
@@ -700,14 +581,12 @@ export class Frontend extends EventEmitter {
700
581
  }
701
582
  res.type('text/plain');
702
583
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
703
- /* istanbul ignore if */
704
584
  if (error) {
705
585
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
706
586
  res.status(500).send('Error downloading the matterbridge log file');
707
587
  }
708
588
  });
709
589
  });
710
- // Endpoint to download the matter log
711
590
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
712
591
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
713
592
  const fs = await import('node:fs');
@@ -722,14 +601,12 @@ export class Frontend extends EventEmitter {
722
601
  }
723
602
  res.type('text/plain');
724
603
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
725
- /* istanbul ignore if */
726
604
  if (error) {
727
605
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
728
606
  res.status(500).send('Error downloading the matter log file');
729
607
  }
730
608
  });
731
609
  });
732
- // Endpoint to download the shelly log
733
610
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
734
611
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
735
612
  const fs = await import('node:fs');
@@ -744,91 +621,75 @@ export class Frontend extends EventEmitter {
744
621
  }
745
622
  res.type('text/plain');
746
623
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
747
- /* istanbul ignore if */
748
624
  if (error) {
749
625
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
750
626
  res.status(500).send('Error downloading Shelly system log file');
751
627
  }
752
628
  });
753
629
  });
754
- // Endpoint to download the matterbridge storage directory
755
630
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
756
631
  this.log.debug('The frontend sent /api/download-mbstorage');
757
632
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
758
633
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
759
- /* istanbul ignore if */
760
634
  if (error) {
761
635
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
762
636
  res.status(500).send('Error downloading the matterbridge storage file');
763
637
  }
764
638
  });
765
639
  });
766
- // Endpoint to download the matter storage file
767
640
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
768
641
  this.log.debug('The frontend sent /api/download-mjstorage');
769
642
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
770
643
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
771
- /* istanbul ignore if */
772
644
  if (error) {
773
645
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
774
646
  res.status(500).send('Error downloading the matter storage zip file');
775
647
  }
776
648
  });
777
649
  });
778
- // Endpoint to download the matterbridge plugin directory
779
650
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
780
651
  this.log.debug('The frontend sent /api/download-pluginstorage');
781
652
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
782
653
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
783
- /* istanbul ignore if */
784
654
  if (error) {
785
655
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
786
656
  res.status(500).send('Error downloading the matterbridge plugin storage file');
787
657
  }
788
658
  });
789
659
  });
790
- // Endpoint to download the matterbridge plugin config files
791
660
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
792
661
  this.log.debug('The frontend sent /api/download-pluginconfig');
793
662
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
794
663
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
795
- /* istanbul ignore if */
796
664
  if (error) {
797
665
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
798
666
  res.status(500).send('Error downloading the matterbridge plugin config file');
799
667
  }
800
668
  });
801
669
  });
802
- // Endpoint to download the matterbridge backup (created with the backup command)
803
670
  this.expressApp.get('/api/download-backup', async (req, res) => {
804
671
  this.log.debug('The frontend sent /api/download-backup');
805
672
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
806
- /* istanbul ignore if */
807
673
  if (error) {
808
674
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
809
675
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
810
676
  }
811
677
  });
812
678
  });
813
- // Endpoint to upload a package
814
679
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
815
680
  const { filename } = req.body;
816
681
  const file = req.file;
817
- /* istanbul ignore if */
818
682
  if (!file || !filename) {
819
683
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
820
684
  res.status(400).send('Invalid request: file and filename are required');
821
685
  return;
822
686
  }
823
687
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
824
- // Define the path where the plugin file will be saved
825
688
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
826
689
  try {
827
- // Move the uploaded file to the specified path
828
690
  const fs = await import('node:fs');
829
691
  await fs.promises.rename(file.path, filePath);
830
692
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
831
- // Install the plugin package
832
693
  if (filename.endsWith('.tgz')) {
833
694
  const { spawnCommand } = await import('./utils/spawn.js');
834
695
  if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
@@ -856,7 +717,6 @@ export class Frontend extends EventEmitter {
856
717
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
857
718
  }
858
719
  });
859
- // Fallback for routing (must be the last route)
860
720
  this.expressApp.use((req, res) => {
861
721
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
862
722
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -867,16 +727,13 @@ export class Frontend extends EventEmitter {
867
727
  async stop() {
868
728
  this.log.debug('Stopping the frontend...');
869
729
  const ws = await import('ws');
870
- // Remove listeners from the express app
871
730
  if (this.expressApp) {
872
731
  this.expressApp.removeAllListeners();
873
732
  this.expressApp = undefined;
874
733
  this.log.debug('Frontend app closed successfully');
875
734
  }
876
- // Close the WebSocket server
877
735
  if (this.webSocketServer) {
878
736
  this.log.debug('Closing WebSocket server...');
879
- // Close all active connections
880
737
  this.webSocketServer.clients.forEach((client) => {
881
738
  if (client.readyState === ws.WebSocket.OPEN) {
882
739
  client.close();
@@ -884,9 +741,7 @@ export class Frontend extends EventEmitter {
884
741
  });
885
742
  await withTimeout(new Promise((resolve) => {
886
743
  this.webSocketServer?.close((error) => {
887
- // istanbul ignore if
888
744
  if (error) {
889
- // istanbul ignore next
890
745
  this.log.error(`Error closing WebSocket server: ${error}`);
891
746
  }
892
747
  else {
@@ -899,27 +754,8 @@ export class Frontend extends EventEmitter {
899
754
  this.webSocketServer.removeAllListeners();
900
755
  this.webSocketServer = undefined;
901
756
  }
902
- // Close the http server
903
757
  if (this.httpServer) {
904
758
  this.log.debug('Closing http server...');
905
- /*
906
- await withTimeout(
907
- new Promise<void>((resolve) => {
908
- this.httpServer?.close((error) => {
909
- if (error) {
910
- // istanbul ignore next
911
- this.log.error(`Error closing http server: ${error}`);
912
- } else {
913
- this.log.debug('Http server closed successfully');
914
- this.emit('server_stopped');
915
- }
916
- resolve();
917
- });
918
- }),
919
- 5000,
920
- false,
921
- );
922
- */
923
759
  this.httpServer.close();
924
760
  this.log.debug('Http server closed successfully');
925
761
  this.listening = false;
@@ -928,27 +764,8 @@ export class Frontend extends EventEmitter {
928
764
  this.httpServer = undefined;
929
765
  this.log.debug('Frontend http server closed successfully');
930
766
  }
931
- // Close the https server
932
767
  if (this.httpsServer) {
933
768
  this.log.debug('Closing https server...');
934
- /*
935
- await withTimeout(
936
- new Promise<void>((resolve) => {
937
- this.httpsServer?.close((error) => {
938
- if (error) {
939
- // istanbul ignore next
940
- this.log.error(`Error closing https server: ${error}`);
941
- } else {
942
- this.log.debug('Https server closed successfully');
943
- this.emit('server_stopped');
944
- }
945
- resolve();
946
- });
947
- }),
948
- 5000,
949
- false,
950
- );
951
- */
952
769
  this.httpsServer.close();
953
770
  this.log.debug('Https server closed successfully');
954
771
  this.listening = false;
@@ -959,13 +776,7 @@ export class Frontend extends EventEmitter {
959
776
  }
960
777
  this.log.debug('Frontend stopped successfully');
961
778
  }
962
- /**
963
- * Retrieves the api settings data.
964
- *
965
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
966
- */
967
779
  async getApiSettings() {
968
- // Update the variable system information properties
969
780
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
970
781
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
971
782
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -975,7 +786,6 @@ export class Frontend extends EventEmitter {
975
786
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
976
787
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
977
788
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
978
- // Create the matterbridge information
979
789
  const info = {
980
790
  homeDirectory: this.matterbridge.homeDirectory,
981
791
  rootDirectory: this.matterbridge.rootDirectory,
@@ -1011,15 +821,9 @@ export class Frontend extends EventEmitter {
1011
821
  };
1012
822
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
1013
823
  }
1014
- /**
1015
- * Retrieves the reachable attribute.
1016
- *
1017
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
1018
- * @returns {boolean} The reachable attribute.
1019
- */
1020
824
  getReachability(device) {
1021
825
  if (this.matterbridge.hasCleanupStarted)
1022
- return false; // Skip if cleanup has started
826
+ return false;
1023
827
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1024
828
  return false;
1025
829
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -1030,15 +834,9 @@ export class Frontend extends EventEmitter {
1030
834
  return true;
1031
835
  return false;
1032
836
  }
1033
- /**
1034
- * Retrieves the power source attribute.
1035
- *
1036
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1037
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1038
- */
1039
837
  getPowerSource(endpoint) {
1040
838
  if (this.matterbridge.hasCleanupStarted)
1041
- return undefined; // Skip if cleanup has started
839
+ return undefined;
1042
840
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1043
841
  return undefined;
1044
842
  const powerSource = (device) => {
@@ -1053,25 +851,16 @@ export class Frontend extends EventEmitter {
1053
851
  }
1054
852
  return;
1055
853
  };
1056
- // Root endpoint
1057
854
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1058
855
  return powerSource(endpoint);
1059
- // Child endpoints
1060
856
  for (const child of endpoint.getChildEndpoints()) {
1061
- // istanbul ignore else
1062
857
  if (child.hasClusterServer(PowerSource.Cluster.id))
1063
858
  return powerSource(child);
1064
859
  }
1065
860
  }
1066
- /**
1067
- * Retrieves the battery level attribute.
1068
- *
1069
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1070
- * @returns {number | undefined} The battery level attribute.
1071
- */
1072
861
  getBatteryLevel(endpoint) {
1073
862
  if (this.matterbridge.hasCleanupStarted)
1074
- return undefined; // Skip if cleanup has started
863
+ return undefined;
1075
864
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1076
865
  return undefined;
1077
866
  const batteryLevel = (device) => {
@@ -1082,27 +871,16 @@ export class Frontend extends EventEmitter {
1082
871
  }
1083
872
  return undefined;
1084
873
  };
1085
- // Root endpoint
1086
874
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1087
875
  return batteryLevel(endpoint);
1088
- // Child endpoints
1089
876
  for (const child of endpoint.getChildEndpoints()) {
1090
- // istanbul ignore else
1091
877
  if (child.hasClusterServer(PowerSource.Cluster.id))
1092
878
  return batteryLevel(child);
1093
879
  }
1094
880
  }
1095
- /**
1096
- * Retrieves the cluster text description from a given device.
1097
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1098
- *
1099
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1100
- * @returns {string} The attributes description of the cluster servers in the device.
1101
- */
1102
881
  getClusterTextFromDevice(device) {
1103
882
  if (this.matterbridge.hasCleanupStarted)
1104
- return ''; // Skip if cleanup has started
1105
- // istanbul ignore else
883
+ return '';
1106
884
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1107
885
  return '';
1108
886
  const getUserLabel = (device) => {
@@ -1112,7 +890,6 @@ export class Frontend extends EventEmitter {
1112
890
  if (composed)
1113
891
  return 'Composed: ' + composed.value;
1114
892
  }
1115
- // istanbul ignore next cause is not reachable
1116
893
  return '';
1117
894
  };
1118
895
  const getFixedLabel = (device) => {
@@ -1122,13 +899,11 @@ export class Frontend extends EventEmitter {
1122
899
  if (composed)
1123
900
  return 'Composed: ' + composed.value;
1124
901
  }
1125
- // istanbul ignore next cause is not reacheable
1126
902
  return '';
1127
903
  };
1128
904
  let attributes = '';
1129
905
  let supportedModes = [];
1130
906
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1131
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1132
907
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1133
908
  return;
1134
909
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1218,17 +993,11 @@ export class Frontend extends EventEmitter {
1218
993
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1219
994
  attributes += `${getUserLabel(device)} `;
1220
995
  });
1221
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1222
996
  return attributes.trimStart().trimEnd();
1223
997
  }
1224
- /**
1225
- * Retrieves the registered plugins sanitized for res.json().
1226
- *
1227
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1228
- */
1229
998
  getPlugins() {
1230
999
  if (this.matterbridge.hasCleanupStarted)
1231
- return []; // Skip if cleanup has started
1000
+ return [];
1232
1001
  const plugins = [];
1233
1002
  for (const plugin of this.matterbridge.plugins.array()) {
1234
1003
  plugins.push({
@@ -1256,27 +1025,18 @@ export class Frontend extends EventEmitter {
1256
1025
  schemaJson: plugin.schemaJson,
1257
1026
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1258
1027
  hasBlackList: plugin.configJson?.blackList !== undefined,
1259
- // Childbridge mode specific data
1260
1028
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1261
1029
  });
1262
1030
  }
1263
1031
  return plugins;
1264
1032
  }
1265
- /**
1266
- * Retrieves the devices from Matterbridge.
1267
- *
1268
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1269
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1270
- */
1271
1033
  getDevices(pluginName) {
1272
1034
  if (this.matterbridge.hasCleanupStarted)
1273
- return []; // Skip if cleanup has started
1035
+ return [];
1274
1036
  const devices = [];
1275
1037
  for (const device of this.matterbridge.devices.array()) {
1276
- // Filter by pluginName if provided
1277
1038
  if (pluginName && pluginName !== device.plugin)
1278
1039
  continue;
1279
- // Check if the device has the required properties
1280
1040
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1281
1041
  continue;
1282
1042
  devices.push({
@@ -1297,39 +1057,24 @@ export class Frontend extends EventEmitter {
1297
1057
  }
1298
1058
  return devices;
1299
1059
  }
1300
- /**
1301
- * Retrieves the clusters from a given plugin and endpoint number.
1302
- *
1303
- * Response for /api/clusters
1304
- *
1305
- * @param {string} pluginName - The name of the plugin.
1306
- * @param {number} endpointNumber - The endpoint number.
1307
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1308
- */
1309
1060
  getClusters(pluginName, endpointNumber) {
1310
1061
  if (this.matterbridge.hasCleanupStarted)
1311
- return; // Skip if cleanup has started
1062
+ return;
1312
1063
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1313
1064
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1314
1065
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1315
1066
  return;
1316
1067
  }
1317
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1318
- // Get the device types from the main endpoint
1319
1068
  const deviceTypes = [];
1320
1069
  const clusters = [];
1321
1070
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1322
1071
  deviceTypes.push(d.deviceType);
1323
1072
  });
1324
- // Get the clusters from the main endpoint
1325
1073
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1326
1074
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1327
1075
  return;
1328
1076
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1329
1077
  return;
1330
- // console.log(
1331
- // `${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}`,
1332
- // );
1333
1078
  clusters.push({
1334
1079
  endpoint: endpoint.number.toString(),
1335
1080
  number: endpoint.number,
@@ -1343,19 +1088,12 @@ export class Frontend extends EventEmitter {
1343
1088
  attributeLocalValue: attributeValue,
1344
1089
  });
1345
1090
  });
1346
- // Get the child endpoints
1347
1091
  const childEndpoints = endpoint.getChildEndpoints();
1348
- // if (childEndpoints.length === 0) {
1349
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1350
- // }
1351
1092
  childEndpoints.forEach((childEndpoint) => {
1352
- // istanbul ignore if cause is not reachable: should never happen but ...
1353
1093
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1354
1094
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1355
1095
  return;
1356
1096
  }
1357
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1358
- // Get the device types of the child endpoint
1359
1097
  const deviceTypes = [];
1360
1098
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1361
1099
  deviceTypes.push(d.deviceType);
@@ -1365,9 +1103,6 @@ export class Frontend extends EventEmitter {
1365
1103
  return;
1366
1104
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1367
1105
  return;
1368
- // console.log(
1369
- // `${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}`,
1370
- // );
1371
1106
  clusters.push({
1372
1107
  endpoint: childEndpoint.number.toString(),
1373
1108
  number: childEndpoint.number,
@@ -1387,7 +1122,6 @@ export class Frontend extends EventEmitter {
1387
1122
  async generateDiagnostic() {
1388
1123
  this.log.debug('Generating diagnostic...');
1389
1124
  const serverNodes = [];
1390
- // istanbul ignore else
1391
1125
  if (this.matterbridge.bridgeMode === 'bridge') {
1392
1126
  if (this.matterbridge.serverNode)
1393
1127
  serverNodes.push(this.matterbridge.serverNode);
@@ -1398,7 +1132,6 @@ export class Frontend extends EventEmitter {
1398
1132
  serverNodes.push(plugin.serverNode);
1399
1133
  }
1400
1134
  }
1401
- // istanbul ignore next
1402
1135
  for (const device of this.matterbridge.devices.array()) {
1403
1136
  if (device.serverNode)
1404
1137
  serverNodes.push(device.serverNode);
@@ -1422,15 +1155,8 @@ export class Frontend extends EventEmitter {
1422
1155
  values: [...serverNodes],
1423
1156
  })));
1424
1157
  delete Logger.destinations.diagnostic;
1425
- await wait(500); // Wait for the log to be written
1158
+ await wait(500);
1426
1159
  }
1427
- /**
1428
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1429
- *
1430
- * @param {WebSocket} client - The websocket client that sent the message.
1431
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1432
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1433
- */
1434
1160
  async wsMessageHandler(client, message) {
1435
1161
  let data;
1436
1162
  const sendResponse = (data) => {
@@ -1448,13 +1174,12 @@ export class Frontend extends EventEmitter {
1448
1174
  client.send(JSON.stringify(data));
1449
1175
  }
1450
1176
  else {
1451
- // istanbul ignore next cause is only a safety check
1452
1177
  this.log.error('Cannot send api response, client not connected');
1453
1178
  }
1454
1179
  };
1455
1180
  try {
1456
1181
  data = JSON.parse(message.toString());
1457
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1182
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1458
1183
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1459
1184
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1460
1185
  return;
@@ -1511,22 +1236,7 @@ export class Frontend extends EventEmitter {
1511
1236
  }
1512
1237
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1513
1238
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1514
- data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
1515
- /*
1516
- const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
1517
- if (plugin) {
1518
- this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1519
- await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
1520
- this.wssSendRestartRequired();
1521
- this.wssSendRefreshRequired('plugins');
1522
- this.wssSendRefreshRequired('devices');
1523
- this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1524
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1525
- } else {
1526
- this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1527
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1528
- }
1529
- */
1239
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1530
1240
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1531
1241
  if (plugin) {
1532
1242
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1539,7 +1249,7 @@ export class Frontend extends EventEmitter {
1539
1249
  this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1540
1250
  return;
1541
1251
  })
1542
- .catch(/* istanbul ignore next */ (_error) => { });
1252
+ .catch((_error) => { });
1543
1253
  }
1544
1254
  else {
1545
1255
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
@@ -1553,10 +1263,6 @@ export class Frontend extends EventEmitter {
1553
1263
  }
1554
1264
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1555
1265
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1556
- /*
1557
- 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);
1558
- await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
1559
- */
1560
1266
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1561
1267
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1562
1268
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1591,7 +1297,7 @@ export class Frontend extends EventEmitter {
1591
1297
  this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
1592
1298
  return;
1593
1299
  })
1594
- .catch(/* istanbul ignore next */ (_error) => { });
1300
+ .catch((_error) => { });
1595
1301
  }
1596
1302
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1597
1303
  }
@@ -1616,7 +1322,6 @@ export class Frontend extends EventEmitter {
1616
1322
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1617
1323
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1618
1324
  if (plugin.serverNode) {
1619
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1620
1325
  await this.matterbridge.stopServerNode(plugin.serverNode);
1621
1326
  plugin.serverNode = undefined;
1622
1327
  }
@@ -1626,20 +1331,18 @@ export class Frontend extends EventEmitter {
1626
1331
  this.matterbridge.devices.remove(device);
1627
1332
  }
1628
1333
  }
1629
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1630
1334
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1631
1335
  await this.matterbridge.createDynamicPlugin(plugin);
1632
1336
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1633
- plugin.restartRequired = false; // Reset plugin restartRequired
1337
+ plugin.restartRequired = false;
1634
1338
  let needRestart = 0;
1635
1339
  for (const plugin of this.matterbridge.plugins) {
1636
1340
  if (plugin.restartRequired)
1637
1341
  needRestart++;
1638
1342
  }
1639
1343
  if (needRestart === 0) {
1640
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1344
+ this.wssSendRestartNotRequired(true);
1641
1345
  }
1642
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1643
1346
  if (plugin.serverNode)
1644
1347
  await this.matterbridge.startServerNode(plugin.serverNode);
1645
1348
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1784,9 +1487,6 @@ export class Frontend extends EventEmitter {
1784
1487
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
1785
1488
  }
1786
1489
  if (data.params.advertise) {
1787
- // TODO: matter.js 0.16.0
1788
- // await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
1789
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1790
1490
  const advertiser = serverNode.env.get(DeviceAdvertiser);
1791
1491
  if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
1792
1492
  await advertiser.advertise(true);
@@ -1848,7 +1548,6 @@ export class Frontend extends EventEmitter {
1848
1548
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
1849
1549
  return;
1850
1550
  }
1851
- // istanbul ignore next
1852
1551
  const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1853
1552
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
1854
1553
  }
@@ -1862,7 +1561,6 @@ export class Frontend extends EventEmitter {
1862
1561
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
1863
1562
  return;
1864
1563
  }
1865
- // istanbul ignore next
1866
1564
  const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1867
1565
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
1868
1566
  }
@@ -1914,22 +1612,22 @@ export class Frontend extends EventEmitter {
1914
1612
  if (isValidString(data.params.value, 4)) {
1915
1613
  this.log.debug('Matterbridge logger level:', data.params.value);
1916
1614
  if (data.params.value === 'Debug') {
1917
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1615
+ await this.matterbridge.setLogLevel("debug");
1918
1616
  }
1919
1617
  else if (data.params.value === 'Info') {
1920
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1618
+ await this.matterbridge.setLogLevel("info");
1921
1619
  }
1922
1620
  else if (data.params.value === 'Notice') {
1923
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1621
+ await this.matterbridge.setLogLevel("notice");
1924
1622
  }
1925
1623
  else if (data.params.value === 'Warn') {
1926
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1624
+ await this.matterbridge.setLogLevel("warn");
1927
1625
  }
1928
1626
  else if (data.params.value === 'Error') {
1929
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1627
+ await this.matterbridge.setLogLevel("error");
1930
1628
  }
1931
1629
  else if (data.params.value === 'Fatal') {
1932
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1630
+ await this.matterbridge.setLogLevel("fatal");
1933
1631
  }
1934
1632
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1935
1633
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1940,7 +1638,6 @@ export class Frontend extends EventEmitter {
1940
1638
  this.log.debug('Matterbridge file log:', data.params.value);
1941
1639
  this.matterbridge.fileLogger = data.params.value;
1942
1640
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1943
- // Create the file logger for matterbridge
1944
1641
  if (data.params.value)
1945
1642
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1946
1643
  else
@@ -1970,12 +1667,11 @@ export class Frontend extends EventEmitter {
1970
1667
  Logger.level = MatterLogLevel.FATAL;
1971
1668
  }
1972
1669
  this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
1973
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1974
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1975
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1976
- callbackLogLevel = "info" /* LogLevel.INFO */;
1977
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1978
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1670
+ let callbackLogLevel = "notice";
1671
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1672
+ callbackLogLevel = "info";
1673
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1674
+ callbackLogLevel = "debug";
1979
1675
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1980
1676
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1981
1677
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -2027,7 +1723,6 @@ export class Frontend extends EventEmitter {
2027
1723
  }
2028
1724
  break;
2029
1725
  case 'setmatterport':
2030
- // eslint-disable-next-line no-case-declarations
2031
1726
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2032
1727
  if (isValidNumber(port, 5540, 5600)) {
2033
1728
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -2047,7 +1742,6 @@ export class Frontend extends EventEmitter {
2047
1742
  }
2048
1743
  break;
2049
1744
  case 'setmatterdiscriminator':
2050
- // eslint-disable-next-line no-case-declarations
2051
1745
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2052
1746
  if (isValidNumber(discriminator, 0, 4095)) {
2053
1747
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -2067,7 +1761,6 @@ export class Frontend extends EventEmitter {
2067
1761
  }
2068
1762
  break;
2069
1763
  case 'setmatterpasscode':
2070
- // eslint-disable-next-line no-case-declarations
2071
1764
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2072
1765
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
2073
1766
  this.matterbridge.passcode = passcode;
@@ -2113,19 +1806,15 @@ export class Frontend extends EventEmitter {
2113
1806
  return;
2114
1807
  }
2115
1808
  const config = plugin.configJson;
2116
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2117
1809
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2118
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2119
1810
  if (select === 'serial')
2120
1811
  this.log.info(`Selected device serial ${data.params.serial}`);
2121
1812
  if (select === 'name')
2122
1813
  this.log.info(`Selected device name ${data.params.name}`);
2123
1814
  if (config && select && (select === 'serial' || select === 'name')) {
2124
- // Remove postfix from the serial if it exists
2125
1815
  if (config.postfix) {
2126
1816
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2127
1817
  }
2128
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2129
1818
  if (isValidArray(config.whiteList, 1)) {
2130
1819
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2131
1820
  config.whiteList.push(data.params.serial);
@@ -2134,7 +1823,6 @@ export class Frontend extends EventEmitter {
2134
1823
  config.whiteList.push(data.params.name);
2135
1824
  }
2136
1825
  }
2137
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2138
1826
  if (isValidArray(config.blackList, 1)) {
2139
1827
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2140
1828
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2162,9 +1850,7 @@ export class Frontend extends EventEmitter {
2162
1850
  return;
2163
1851
  }
2164
1852
  const config = plugin.configJson;
2165
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2166
1853
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2167
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2168
1854
  if (select === 'serial')
2169
1855
  this.log.info(`Unselected device serial ${data.params.serial}`);
2170
1856
  if (select === 'name')
@@ -2173,7 +1859,6 @@ export class Frontend extends EventEmitter {
2173
1859
  if (config.postfix) {
2174
1860
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2175
1861
  }
2176
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2177
1862
  if (isValidArray(config.whiteList, 1)) {
2178
1863
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2179
1864
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2182,7 +1867,6 @@ export class Frontend extends EventEmitter {
2182
1867
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2183
1868
  }
2184
1869
  }
2185
- // Add the serial to the blackList
2186
1870
  if (isValidArray(config.blackList)) {
2187
1871
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2188
1872
  config.blackList.push(data.params.serial);
@@ -2205,7 +1889,6 @@ export class Frontend extends EventEmitter {
2205
1889
  }
2206
1890
  }
2207
1891
  else {
2208
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2209
1892
  const localData = data;
2210
1893
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2211
1894
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2215,46 +1898,23 @@ export class Frontend extends EventEmitter {
2215
1898
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2216
1899
  }
2217
1900
  }
2218
- /**
2219
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2220
- *
2221
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2222
- * @param {string} time - The time string of the message
2223
- * @param {string} name - The logger name of the message
2224
- * @param {string} message - The content of the message.
2225
- *
2226
- * @remarks
2227
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2228
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2229
- * The function sends the message to all connected clients.
2230
- */
2231
1901
  wssSendLogMessage(level, time, name, message) {
2232
1902
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2233
1903
  return;
2234
1904
  if (!level || !time || !name || !message)
2235
1905
  return;
2236
- // Remove ANSI escape codes from the message
2237
- // eslint-disable-next-line no-control-regex
2238
1906
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2239
- // Remove leading asterisks from the message
2240
1907
  message = message.replace(/^\*+/, '');
2241
- // Replace all occurrences of \t and \n
2242
1908
  message = message.replace(/[\t\n]/g, '');
2243
- // Remove non-printable characters
2244
- // eslint-disable-next-line no-control-regex
2245
1909
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2246
- // Replace all occurrences of \" with "
2247
1910
  message = message.replace(/\\"/g, '"');
2248
- // Define the maximum allowed length for continuous characters without a space
2249
1911
  const maxContinuousLength = 100;
2250
1912
  const keepStartLength = 20;
2251
1913
  const keepEndLength = 20;
2252
- // Split the message into words
2253
1914
  if (level !== 'spawn') {
2254
1915
  message = message
2255
1916
  .split(' ')
2256
1917
  .map((word) => {
2257
- // If the word length exceeds the max continuous length, insert spaces and truncate
2258
1918
  if (word.length > maxContinuousLength) {
2259
1919
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2260
1920
  }
@@ -2262,34 +1922,14 @@ export class Frontend extends EventEmitter {
2262
1922
  })
2263
1923
  .join(' ');
2264
1924
  }
2265
- // Send the message to all connected clients
2266
1925
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2267
1926
  }
2268
- /**
2269
- * Sends a need to refresh WebSocket message to all connected clients.
2270
- *
2271
- * @param {string} changed - The changed value.
2272
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2273
- * possible values for changed:
2274
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2275
- * - 'plugins'
2276
- * - 'devices'
2277
- * - 'matter' with param 'matter' (QRDiv component)
2278
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2279
- */
2280
1927
  wssSendRefreshRequired(changed, params) {
2281
1928
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2282
1929
  return;
2283
1930
  this.log.debug('Sending a refresh required message to all connected clients');
2284
- // Send the message to all connected clients
2285
1931
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2286
1932
  }
2287
- /**
2288
- * Sends a need to restart WebSocket message to all connected clients.
2289
- *
2290
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2291
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2292
- */
2293
1933
  wssSendRestartRequired(snackbar = true, fixed = false) {
2294
1934
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2295
1935
  return;
@@ -2298,14 +1938,8 @@ export class Frontend extends EventEmitter {
2298
1938
  this.matterbridge.fixedRestartRequired = fixed;
2299
1939
  if (snackbar === true)
2300
1940
  this.wssSendSnackbarMessage(`Restart required`, 0);
2301
- // Send the message to all connected clients
2302
1941
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2303
1942
  }
2304
- /**
2305
- * Sends a no need to restart WebSocket message to all connected clients.
2306
- *
2307
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2308
- */
2309
1943
  wssSendRestartNotRequired(snackbar = true) {
2310
1944
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2311
1945
  return;
@@ -2313,145 +1947,64 @@ export class Frontend extends EventEmitter {
2313
1947
  this.matterbridge.restartRequired = false;
2314
1948
  if (snackbar === true)
2315
1949
  this.wssSendCloseSnackbarMessage(`Restart required`);
2316
- // Send the message to all connected clients
2317
1950
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2318
1951
  }
2319
- /**
2320
- * Sends a need to update WebSocket message to all connected clients.
2321
- *
2322
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2323
- */
2324
1952
  wssSendUpdateRequired(devVersion = false) {
2325
1953
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2326
1954
  return;
2327
1955
  this.log.debug('Sending an update required message to all connected clients');
2328
1956
  this.matterbridge.updateRequired = true;
2329
- // Send the message to all connected clients
2330
1957
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2331
1958
  }
2332
- /**
2333
- * Sends a cpu update message to all connected clients.
2334
- *
2335
- * @param {number} cpuUsage - The CPU usage percentage to send.
2336
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2337
- */
2338
1959
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2339
1960
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2340
1961
  return;
2341
- // istanbul ignore else
2342
1962
  if (hasParameter('debug'))
2343
1963
  this.log.debug('Sending a cpu update message to all connected clients');
2344
- // Send the message to all connected clients
2345
1964
  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 } });
2346
1965
  }
2347
- /**
2348
- * Sends a memory update message to all connected clients.
2349
- *
2350
- * @param {string} totalMemory - The total memory in bytes.
2351
- * @param {string} freeMemory - The free memory in bytes.
2352
- * @param {string} rss - The resident set size in bytes.
2353
- * @param {string} heapTotal - The total heap memory in bytes.
2354
- * @param {string} heapUsed - The used heap memory in bytes.
2355
- * @param {string} external - The external memory in bytes.
2356
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2357
- */
2358
1966
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2359
1967
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2360
1968
  return;
2361
- // istanbul ignore else
2362
1969
  if (hasParameter('debug'))
2363
1970
  this.log.debug('Sending a memory update message to all connected clients');
2364
- // Send the message to all connected clients
2365
1971
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2366
1972
  }
2367
- /**
2368
- * Sends an uptime update message to all connected clients.
2369
- *
2370
- * @param {string} systemUptime - The system uptime in a human-readable format.
2371
- * @param {string} processUptime - The process uptime in a human-readable format.
2372
- */
2373
1973
  wssSendUptimeUpdate(systemUptime, processUptime) {
2374
1974
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2375
1975
  return;
2376
- // istanbul ignore else
2377
1976
  if (hasParameter('debug'))
2378
1977
  this.log.debug('Sending a uptime update message to all connected clients');
2379
- // Send the message to all connected clients
2380
1978
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2381
1979
  }
2382
- /**
2383
- * Sends an open snackbar message to all connected clients.
2384
- *
2385
- * @param {string} message - The message to send.
2386
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2387
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2388
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2389
- *
2390
- * @remarks
2391
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2392
- */
2393
1980
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2394
1981
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2395
1982
  return;
2396
1983
  this.log.debug('Sending a snackbar message to all connected clients');
2397
- // Send the message to all connected clients
2398
1984
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2399
1985
  }
2400
- /**
2401
- * Sends a close snackbar message to all connected clients.
2402
- * It will close the snackbar message with the same message and timeout = 0.
2403
- *
2404
- * @param {string} message - The message to send.
2405
- */
2406
1986
  wssSendCloseSnackbarMessage(message) {
2407
1987
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2408
1988
  return;
2409
1989
  this.log.debug('Sending a close snackbar message to all connected clients');
2410
- // Send the message to all connected clients
2411
1990
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2412
1991
  }
2413
- /**
2414
- * Sends an attribute update message to all connected WebSocket clients.
2415
- *
2416
- * @param {string | undefined} plugin - The name of the plugin.
2417
- * @param {string | undefined} serialNumber - The serial number of the device.
2418
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2419
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2420
- * @param {string} id - The endpoint id where the attribute belongs.
2421
- * @param {string} cluster - The cluster name where the attribute belongs.
2422
- * @param {string} attribute - The name of the attribute that changed.
2423
- * @param {number | string | boolean} value - The new value of the attribute.
2424
- *
2425
- * @remarks
2426
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2427
- * with the updated attribute information.
2428
- */
2429
1992
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2430
1993
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2431
1994
  return;
2432
1995
  this.log.debug('Sending an attribute update message to all connected clients');
2433
- // Send the message to all connected clients
2434
1996
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2435
1997
  }
2436
- /**
2437
- * Sends a message to all connected clients.
2438
- * This is an helper function to send a broadcast message to all connected clients.
2439
- *
2440
- * @param {WsMessageBroadcast} msg - The message to send.
2441
- */
2442
1998
  wssBroadcastMessage(msg) {
2443
1999
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2444
2000
  return;
2445
- // Send the message to all connected clients
2446
2001
  const stringifiedMsg = JSON.stringify(msg);
2447
2002
  if (msg.method !== 'log')
2448
2003
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2449
2004
  this.webSocketServer?.clients.forEach((client) => {
2450
- // istanbul ignore else
2451
2005
  if (client.readyState === client.OPEN) {
2452
2006
  client.send(stringifiedMsg);
2453
2007
  }
2454
2008
  });
2455
2009
  }
2456
2010
  }
2457
- //# sourceMappingURL=frontend.js.map