matterbridge 3.3.4-dev-20251021-7651f57 → 3.3.4

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 (304) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/README-SERVICE-LOCAL.md +226 -0
  3. package/dist/broadcastServer.d.ts +112 -0
  4. package/dist/broadcastServer.d.ts.map +1 -0
  5. package/dist/broadcastServer.js +100 -6
  6. package/dist/broadcastServer.js.map +1 -0
  7. package/dist/broadcastServerTypes.d.ts +793 -0
  8. package/dist/broadcastServerTypes.d.ts.map +1 -0
  9. package/dist/broadcastServerTypes.js +24 -0
  10. package/dist/broadcastServerTypes.js.map +1 -0
  11. package/dist/cli.d.ts +30 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +100 -2
  14. package/dist/cli.js.map +1 -0
  15. package/dist/cliEmitter.d.ts +50 -0
  16. package/dist/cliEmitter.d.ts.map +1 -0
  17. package/dist/cliEmitter.js +37 -0
  18. package/dist/cliEmitter.js.map +1 -0
  19. package/dist/cliHistory.d.ts +48 -0
  20. package/dist/cliHistory.d.ts.map +1 -0
  21. package/dist/cliHistory.js +38 -0
  22. package/dist/cliHistory.js.map +1 -0
  23. package/dist/clusters/export.d.ts +2 -0
  24. package/dist/clusters/export.d.ts.map +1 -0
  25. package/dist/clusters/export.js +2 -0
  26. package/dist/clusters/export.js.map +1 -0
  27. package/dist/defaultConfigSchema.d.ts +28 -0
  28. package/dist/defaultConfigSchema.d.ts.map +1 -0
  29. package/dist/defaultConfigSchema.js +24 -0
  30. package/dist/defaultConfigSchema.js.map +1 -0
  31. package/dist/deviceManager.d.ts +117 -0
  32. package/dist/deviceManager.d.ts.map +1 -0
  33. package/dist/deviceManager.js +126 -3
  34. package/dist/deviceManager.js.map +1 -0
  35. package/dist/devices/airConditioner.d.ts +98 -0
  36. package/dist/devices/airConditioner.d.ts.map +1 -0
  37. package/dist/devices/airConditioner.js +57 -0
  38. package/dist/devices/airConditioner.js.map +1 -0
  39. package/dist/devices/batteryStorage.d.ts +48 -0
  40. package/dist/devices/batteryStorage.d.ts.map +1 -0
  41. package/dist/devices/batteryStorage.js +48 -1
  42. package/dist/devices/batteryStorage.js.map +1 -0
  43. package/dist/devices/cooktop.d.ts +60 -0
  44. package/dist/devices/cooktop.d.ts.map +1 -0
  45. package/dist/devices/cooktop.js +55 -0
  46. package/dist/devices/cooktop.js.map +1 -0
  47. package/dist/devices/dishwasher.d.ts +71 -0
  48. package/dist/devices/dishwasher.d.ts.map +1 -0
  49. package/dist/devices/dishwasher.js +57 -0
  50. package/dist/devices/dishwasher.js.map +1 -0
  51. package/dist/devices/evse.d.ts +76 -0
  52. package/dist/devices/evse.d.ts.map +1 -0
  53. package/dist/devices/evse.js +74 -10
  54. package/dist/devices/evse.js.map +1 -0
  55. package/dist/devices/export.d.ts +17 -0
  56. package/dist/devices/export.d.ts.map +1 -0
  57. package/dist/devices/export.js +5 -0
  58. package/dist/devices/export.js.map +1 -0
  59. package/dist/devices/extractorHood.d.ts +46 -0
  60. package/dist/devices/extractorHood.d.ts.map +1 -0
  61. package/dist/devices/extractorHood.js +42 -0
  62. package/dist/devices/extractorHood.js.map +1 -0
  63. package/dist/devices/heatPump.d.ts +47 -0
  64. package/dist/devices/heatPump.d.ts.map +1 -0
  65. package/dist/devices/heatPump.js +50 -2
  66. package/dist/devices/heatPump.js.map +1 -0
  67. package/dist/devices/laundryDryer.d.ts +67 -0
  68. package/dist/devices/laundryDryer.d.ts.map +1 -0
  69. package/dist/devices/laundryDryer.js +62 -3
  70. package/dist/devices/laundryDryer.js.map +1 -0
  71. package/dist/devices/laundryWasher.d.ts +81 -0
  72. package/dist/devices/laundryWasher.d.ts.map +1 -0
  73. package/dist/devices/laundryWasher.js +70 -4
  74. package/dist/devices/laundryWasher.js.map +1 -0
  75. package/dist/devices/microwaveOven.d.ts +168 -0
  76. package/dist/devices/microwaveOven.d.ts.map +1 -0
  77. package/dist/devices/microwaveOven.js +88 -5
  78. package/dist/devices/microwaveOven.js.map +1 -0
  79. package/dist/devices/oven.d.ts +105 -0
  80. package/dist/devices/oven.d.ts.map +1 -0
  81. package/dist/devices/oven.js +85 -0
  82. package/dist/devices/oven.js.map +1 -0
  83. package/dist/devices/refrigerator.d.ts +118 -0
  84. package/dist/devices/refrigerator.d.ts.map +1 -0
  85. package/dist/devices/refrigerator.js +102 -0
  86. package/dist/devices/refrigerator.js.map +1 -0
  87. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  88. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  89. package/dist/devices/roboticVacuumCleaner.js +100 -9
  90. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  91. package/dist/devices/solarPower.d.ts +40 -0
  92. package/dist/devices/solarPower.d.ts.map +1 -0
  93. package/dist/devices/solarPower.js +38 -0
  94. package/dist/devices/solarPower.js.map +1 -0
  95. package/dist/devices/speaker.d.ts +87 -0
  96. package/dist/devices/speaker.d.ts.map +1 -0
  97. package/dist/devices/speaker.js +84 -0
  98. package/dist/devices/speaker.js.map +1 -0
  99. package/dist/devices/temperatureControl.d.ts +166 -0
  100. package/dist/devices/temperatureControl.d.ts.map +1 -0
  101. package/dist/devices/temperatureControl.js +24 -3
  102. package/dist/devices/temperatureControl.js.map +1 -0
  103. package/dist/devices/waterHeater.d.ts +111 -0
  104. package/dist/devices/waterHeater.d.ts.map +1 -0
  105. package/dist/devices/waterHeater.js +82 -2
  106. package/dist/devices/waterHeater.js.map +1 -0
  107. package/dist/dgram/coap.d.ts +205 -0
  108. package/dist/dgram/coap.d.ts.map +1 -0
  109. package/dist/dgram/coap.js +126 -13
  110. package/dist/dgram/coap.js.map +1 -0
  111. package/dist/dgram/dgram.d.ts +141 -0
  112. package/dist/dgram/dgram.d.ts.map +1 -0
  113. package/dist/dgram/dgram.js +114 -2
  114. package/dist/dgram/dgram.js.map +1 -0
  115. package/dist/dgram/mb_coap.d.ts +24 -0
  116. package/dist/dgram/mb_coap.d.ts.map +1 -0
  117. package/dist/dgram/mb_coap.js +41 -3
  118. package/dist/dgram/mb_coap.js.map +1 -0
  119. package/dist/dgram/mb_mdns.d.ts +24 -0
  120. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  121. package/dist/dgram/mb_mdns.js +80 -15
  122. package/dist/dgram/mb_mdns.js.map +1 -0
  123. package/dist/dgram/mdns.d.ts +290 -0
  124. package/dist/dgram/mdns.d.ts.map +1 -0
  125. package/dist/dgram/mdns.js +299 -137
  126. package/dist/dgram/mdns.js.map +1 -0
  127. package/dist/dgram/multicast.d.ts +67 -0
  128. package/dist/dgram/multicast.d.ts.map +1 -0
  129. package/dist/dgram/multicast.js +62 -1
  130. package/dist/dgram/multicast.js.map +1 -0
  131. package/dist/dgram/unicast.d.ts +56 -0
  132. package/dist/dgram/unicast.d.ts.map +1 -0
  133. package/dist/dgram/unicast.js +54 -0
  134. package/dist/dgram/unicast.js.map +1 -0
  135. package/dist/frontend.d.ts +235 -0
  136. package/dist/frontend.d.ts.map +1 -0
  137. package/dist/frontend.js +441 -39
  138. package/dist/frontend.js.map +1 -0
  139. package/dist/frontendTypes.d.ts +529 -0
  140. package/dist/frontendTypes.d.ts.map +1 -0
  141. package/dist/frontendTypes.js +45 -0
  142. package/dist/frontendTypes.js.map +1 -0
  143. package/dist/helpers.d.ts +48 -0
  144. package/dist/helpers.d.ts.map +1 -0
  145. package/dist/helpers.js +53 -0
  146. package/dist/helpers.js.map +1 -0
  147. package/dist/index.d.ts +33 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +25 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/logger/export.d.ts +2 -0
  152. package/dist/logger/export.d.ts.map +1 -0
  153. package/dist/logger/export.js +1 -0
  154. package/dist/logger/export.js.map +1 -0
  155. package/dist/matter/behaviors.d.ts +2 -0
  156. package/dist/matter/behaviors.d.ts.map +1 -0
  157. package/dist/matter/behaviors.js +2 -0
  158. package/dist/matter/behaviors.js.map +1 -0
  159. package/dist/matter/clusters.d.ts +2 -0
  160. package/dist/matter/clusters.d.ts.map +1 -0
  161. package/dist/matter/clusters.js +2 -0
  162. package/dist/matter/clusters.js.map +1 -0
  163. package/dist/matter/devices.d.ts +2 -0
  164. package/dist/matter/devices.d.ts.map +1 -0
  165. package/dist/matter/devices.js +2 -0
  166. package/dist/matter/devices.js.map +1 -0
  167. package/dist/matter/endpoints.d.ts +2 -0
  168. package/dist/matter/endpoints.d.ts.map +1 -0
  169. package/dist/matter/endpoints.js +2 -0
  170. package/dist/matter/endpoints.js.map +1 -0
  171. package/dist/matter/export.d.ts +5 -0
  172. package/dist/matter/export.d.ts.map +1 -0
  173. package/dist/matter/export.js +3 -0
  174. package/dist/matter/export.js.map +1 -0
  175. package/dist/matter/types.d.ts +3 -0
  176. package/dist/matter/types.d.ts.map +1 -0
  177. package/dist/matter/types.js +3 -0
  178. package/dist/matter/types.js.map +1 -0
  179. package/dist/matterbridge.d.ts +475 -0
  180. package/dist/matterbridge.d.ts.map +1 -0
  181. package/dist/matterbridge.js +833 -51
  182. package/dist/matterbridge.js.map +1 -0
  183. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  184. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  185. package/dist/matterbridgeAccessoryPlatform.js +37 -0
  186. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  187. package/dist/matterbridgeBehaviors.d.ts +2404 -0
  188. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  189. package/dist/matterbridgeBehaviors.js +68 -5
  190. package/dist/matterbridgeBehaviors.js.map +1 -0
  191. package/dist/matterbridgeDeviceTypes.d.ts +770 -0
  192. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  193. package/dist/matterbridgeDeviceTypes.js +638 -17
  194. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  195. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  196. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  197. package/dist/matterbridgeDynamicPlatform.js +37 -0
  198. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  199. package/dist/matterbridgeEndpoint.d.ts +1550 -0
  200. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  201. package/dist/matterbridgeEndpoint.js +1403 -53
  202. package/dist/matterbridgeEndpoint.js.map +1 -0
  203. package/dist/matterbridgeEndpointHelpers.d.ts +758 -0
  204. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  205. package/dist/matterbridgeEndpointHelpers.js +464 -19
  206. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  207. package/dist/matterbridgePlatform.d.ts +402 -0
  208. package/dist/matterbridgePlatform.d.ts.map +1 -0
  209. package/dist/matterbridgePlatform.js +341 -1
  210. package/dist/matterbridgePlatform.js.map +1 -0
  211. package/dist/matterbridgeTypes.d.ts +226 -0
  212. package/dist/matterbridgeTypes.d.ts.map +1 -0
  213. package/dist/matterbridgeTypes.js +26 -0
  214. package/dist/matterbridgeTypes.js.map +1 -0
  215. package/dist/pluginManager.d.ts +347 -0
  216. package/dist/pluginManager.d.ts.map +1 -0
  217. package/dist/pluginManager.js +320 -4
  218. package/dist/pluginManager.js.map +1 -0
  219. package/dist/shelly.d.ts +174 -0
  220. package/dist/shelly.d.ts.map +1 -0
  221. package/dist/shelly.js +168 -7
  222. package/dist/shelly.js.map +1 -0
  223. package/dist/storage/export.d.ts +2 -0
  224. package/dist/storage/export.d.ts.map +1 -0
  225. package/dist/storage/export.js +1 -0
  226. package/dist/storage/export.js.map +1 -0
  227. package/dist/update.d.ts +75 -0
  228. package/dist/update.d.ts.map +1 -0
  229. package/dist/update.js +69 -0
  230. package/dist/update.js.map +1 -0
  231. package/dist/utils/colorUtils.d.ts +99 -0
  232. package/dist/utils/colorUtils.d.ts.map +1 -0
  233. package/dist/utils/colorUtils.js +97 -2
  234. package/dist/utils/colorUtils.js.map +1 -0
  235. package/dist/utils/commandLine.d.ts +66 -0
  236. package/dist/utils/commandLine.d.ts.map +1 -0
  237. package/dist/utils/commandLine.js +60 -0
  238. package/dist/utils/commandLine.js.map +1 -0
  239. package/dist/utils/copyDirectory.d.ts +33 -0
  240. package/dist/utils/copyDirectory.d.ts.map +1 -0
  241. package/dist/utils/copyDirectory.js +38 -1
  242. package/dist/utils/copyDirectory.js.map +1 -0
  243. package/dist/utils/createDirectory.d.ts +34 -0
  244. package/dist/utils/createDirectory.d.ts.map +1 -0
  245. package/dist/utils/createDirectory.js +33 -0
  246. package/dist/utils/createDirectory.js.map +1 -0
  247. package/dist/utils/createZip.d.ts +39 -0
  248. package/dist/utils/createZip.d.ts.map +1 -0
  249. package/dist/utils/createZip.js +47 -2
  250. package/dist/utils/createZip.js.map +1 -0
  251. package/dist/utils/deepCopy.d.ts +32 -0
  252. package/dist/utils/deepCopy.d.ts.map +1 -0
  253. package/dist/utils/deepCopy.js +39 -0
  254. package/dist/utils/deepCopy.js.map +1 -0
  255. package/dist/utils/deepEqual.d.ts +54 -0
  256. package/dist/utils/deepEqual.d.ts.map +1 -0
  257. package/dist/utils/deepEqual.js +72 -1
  258. package/dist/utils/deepEqual.js.map +1 -0
  259. package/dist/utils/error.d.ts +44 -0
  260. package/dist/utils/error.d.ts.map +1 -0
  261. package/dist/utils/error.js +43 -1
  262. package/dist/utils/error.js.map +1 -0
  263. package/dist/utils/export.d.ts +13 -0
  264. package/dist/utils/export.d.ts.map +1 -0
  265. package/dist/utils/export.js +1 -0
  266. package/dist/utils/export.js.map +1 -0
  267. package/dist/utils/format.d.ts +53 -0
  268. package/dist/utils/format.d.ts.map +1 -0
  269. package/dist/utils/format.js +49 -0
  270. package/dist/utils/format.js.map +1 -0
  271. package/dist/utils/hex.d.ts +89 -0
  272. package/dist/utils/hex.d.ts.map +1 -0
  273. package/dist/utils/hex.js +124 -0
  274. package/dist/utils/hex.js.map +1 -0
  275. package/dist/utils/inspector.d.ts +87 -0
  276. package/dist/utils/inspector.d.ts.map +1 -0
  277. package/dist/utils/inspector.js +69 -1
  278. package/dist/utils/inspector.js.map +1 -0
  279. package/dist/utils/isvalid.d.ts +103 -0
  280. package/dist/utils/isvalid.d.ts.map +1 -0
  281. package/dist/utils/isvalid.js +101 -0
  282. package/dist/utils/isvalid.js.map +1 -0
  283. package/dist/utils/jestHelpers.d.ts +139 -0
  284. package/dist/utils/jestHelpers.d.ts.map +1 -0
  285. package/dist/utils/jestHelpers.js +153 -3
  286. package/dist/utils/jestHelpers.js.map +1 -0
  287. package/dist/utils/network.d.ts +101 -0
  288. package/dist/utils/network.d.ts.map +1 -0
  289. package/dist/utils/network.js +96 -5
  290. package/dist/utils/network.js.map +1 -0
  291. package/dist/utils/spawn.d.ts +35 -0
  292. package/dist/utils/spawn.d.ts.map +1 -0
  293. package/dist/utils/spawn.js +71 -0
  294. package/dist/utils/spawn.js.map +1 -0
  295. package/dist/utils/tracker.d.ts +108 -0
  296. package/dist/utils/tracker.d.ts.map +1 -0
  297. package/dist/utils/tracker.js +64 -1
  298. package/dist/utils/tracker.js.map +1 -0
  299. package/dist/utils/wait.d.ts +54 -0
  300. package/dist/utils/wait.d.ts.map +1 -0
  301. package/dist/utils/wait.js +60 -8
  302. package/dist/utils/wait.js.map +1 -0
  303. package/npm-shrinkwrap.json +5 -5
  304. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,9 +1,35 @@
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @created 2025-01-13
7
+ * @version 1.3.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2025, 2026, 2027 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // eslint-disable-next-line no-console
1
25
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
26
  console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
27
+ // Node modules
3
28
  import os from 'node:os';
4
29
  import path from 'node:path';
5
30
  import EventEmitter from 'node:events';
6
- import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
31
+ // AnsiLogger module
32
+ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
7
33
  import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
8
34
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
9
35
  import { FabricIndex } from '@matter/types/datatype';
@@ -34,7 +60,7 @@ export class Frontend extends EventEmitter {
34
60
  constructor(matterbridge) {
35
61
  super();
36
62
  this.matterbridge = matterbridge;
37
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
63
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
38
64
  this.log.logNameColor = '\x1b[38;5;97m';
39
65
  this.server = new BroadcastServer('frontend', this.log);
40
66
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -44,7 +70,7 @@ export class Frontend extends EventEmitter {
44
70
  }
45
71
  async msgHandler(msg) {
46
72
  if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'frontend')) {
47
- this.log.debug(`**Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
73
+ this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
48
74
  switch (msg.type) {
49
75
  case 'frontend_start':
50
76
  await this.start(msg.params.port);
@@ -55,11 +81,11 @@ export class Frontend extends EventEmitter {
55
81
  this.server.respond({ ...msg, response: { success: true } });
56
82
  break;
57
83
  default:
58
- this.log.warn(`Unknown broadcast request ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
84
+ this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
59
85
  }
60
86
  }
61
87
  if (this.server.isWorkerResponse(msg, msg.type)) {
62
- this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
88
+ this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
63
89
  switch (msg.type) {
64
90
  case 'plugins_install':
65
91
  this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
@@ -84,7 +110,7 @@ export class Frontend extends EventEmitter {
84
110
  }
85
111
  break;
86
112
  default:
87
- this.log.warn(`Unknown broadcast response ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
113
+ this.log.debug(`Unknown broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
88
114
  }
89
115
  }
90
116
  }
@@ -94,13 +120,42 @@ export class Frontend extends EventEmitter {
94
120
  async start(port = 8283) {
95
121
  this.port = port;
96
122
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
123
+ // Initialize multer with the upload directory
97
124
  const multer = await import('multer');
98
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
125
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
99
126
  const upload = multer.default({ dest: uploadDir });
127
+ // Create the express app that serves the frontend
100
128
  const express = await import('express');
101
129
  this.expressApp = express.default();
130
+ // Inject logging/debug wrapper for route/middleware registration
131
+ /*
132
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
133
+ for (const method of methods) {
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
138
+ try {
139
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
140
+ return original(path, ...rest);
141
+ } catch (err) {
142
+ console.error(`[ERROR] Failed to register route: ${path}`);
143
+ throw err;
144
+ }
145
+ };
146
+ }
147
+ */
148
+ // Log all requests to the server for debugging
149
+ /*
150
+ this.expressApp.use((req, res, next) => {
151
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
152
+ next();
153
+ });
154
+ */
155
+ // Serve static files from 'frontend/build' directory
102
156
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
103
157
  if (!hasParameter('ssl')) {
158
+ // Create an HTTP server and attach the express app
104
159
  const http = await import('node:http');
105
160
  try {
106
161
  this.log.debug(`Creating HTTP server...`);
@@ -111,6 +166,7 @@ export class Frontend extends EventEmitter {
111
166
  this.emit('server_error', error);
112
167
  return;
113
168
  }
169
+ // Listen on the specified port
114
170
  if (hasParameter('ingress')) {
115
171
  this.httpServer.listen(this.port, '0.0.0.0', () => {
116
172
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -143,6 +199,7 @@ export class Frontend extends EventEmitter {
143
199
  });
144
200
  }
145
201
  else {
202
+ // SSL is enabled, load the certificate and the private key
146
203
  let cert;
147
204
  let key;
148
205
  let ca;
@@ -152,6 +209,7 @@ export class Frontend extends EventEmitter {
152
209
  let httpsServerOptions = {};
153
210
  const fs = await import('node:fs');
154
211
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
212
+ // Load the p12 certificate and the passphrase
155
213
  try {
156
214
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
157
215
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -163,7 +221,7 @@ export class Frontend extends EventEmitter {
163
221
  }
164
222
  try {
165
223
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
166
- passphrase = passphrase.trim();
224
+ passphrase = passphrase.trim(); // Ensure no extra characters
167
225
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
168
226
  }
169
227
  catch (error) {
@@ -174,6 +232,7 @@ export class Frontend extends EventEmitter {
174
232
  httpsServerOptions = { pfx, passphrase };
175
233
  }
176
234
  else {
235
+ // 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.
177
236
  try {
178
237
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
179
238
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -203,9 +262,10 @@ export class Frontend extends EventEmitter {
203
262
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
204
263
  }
205
264
  if (hasParameter('mtls')) {
206
- httpsServerOptions.requestCert = true;
207
- httpsServerOptions.rejectUnauthorized = true;
265
+ httpsServerOptions.requestCert = true; // Request client certificate
266
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
208
267
  }
268
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
209
269
  const https = await import('node:https');
210
270
  try {
211
271
  this.log.debug(`Creating HTTPS server...`);
@@ -216,6 +276,7 @@ export class Frontend extends EventEmitter {
216
276
  this.emit('server_error', error);
217
277
  return;
218
278
  }
279
+ // Listen on the specified port
219
280
  if (hasParameter('ingress')) {
220
281
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
221
282
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -247,16 +308,41 @@ export class Frontend extends EventEmitter {
247
308
  return;
248
309
  });
249
310
  }
311
+ // Load the stored password
312
+ /*
313
+ let storedPassword = '';
314
+ try {
315
+ if (!this.matterbridge.nodeContext) throw new Error('nodeContext not found');
316
+ storedPassword = await this.matterbridge.nodeContext.get('password', '');
317
+ } catch (error) {
318
+ inspectError(this.log, 'Error getting password', error);
319
+ return;
320
+ }
321
+ */
322
+ // Create a WebSocket server and attach it to the http or https server
250
323
  const ws = await import('ws');
251
324
  this.log.debug(`Creating WebSocketServer...`);
252
325
  this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
253
326
  this.webSocketServer.on('connection', (ws, request) => {
254
327
  const clientIp = request.socket.remoteAddress;
255
- let callbackLogLevel = "notice";
256
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
257
- callbackLogLevel = "info";
258
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
259
- callbackLogLevel = "debug";
328
+ /*
329
+ if (storedPassword !== '') {
330
+ // Check for the password in the query parameters
331
+ const url = new URL(request.url ?? '', `http://${request.headers.host}`);
332
+ const password = url.searchParams.get('password');
333
+ if (password !== storedPassword) {
334
+ this.log.error(`WebSocket client "${clientIp}" failed authentication: ${storedPassword}-${password}`);
335
+ // ws.close();
336
+ // return;
337
+ }
338
+ }
339
+ */
340
+ // Set the global logger callback for the WebSocketServer
341
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
342
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
343
+ callbackLogLevel = "info" /* LogLevel.INFO */;
344
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
345
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
260
346
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
261
347
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
262
348
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -278,6 +364,7 @@ export class Frontend extends EventEmitter {
278
364
  }
279
365
  });
280
366
  ws.on('error', (error) => {
367
+ // istanbul ignore next
281
368
  this.log.error(`WebSocket client error: ${error}`);
282
369
  });
283
370
  });
@@ -291,6 +378,7 @@ export class Frontend extends EventEmitter {
291
378
  this.webSocketServer.on('error', (ws, error) => {
292
379
  this.log.error(`WebSocketServer error: ${error}`);
293
380
  });
381
+ // Subscribe to cli events
294
382
  cliEmitter.removeAllListeners();
295
383
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
296
384
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -301,6 +389,8 @@ export class Frontend extends EventEmitter {
301
389
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
302
390
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
303
391
  });
392
+ // Endpoint to validate login code
393
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
304
394
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
305
395
  const { password } = req.body;
306
396
  this.log.debug('The frontend sent /api/login', password);
@@ -319,23 +409,27 @@ export class Frontend extends EventEmitter {
319
409
  this.log.warn('/api/login error wrong password');
320
410
  res.json({ valid: false });
321
411
  }
412
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
322
413
  }
323
414
  catch (error) {
324
415
  this.log.error('/api/login error getting password');
325
416
  res.json({ valid: false });
326
417
  }
327
418
  });
419
+ // Endpoint to provide health check for docker
328
420
  this.expressApp.get('/health', (req, res) => {
329
421
  this.log.debug('Express received /health');
330
422
  const healthStatus = {
331
- status: 'ok',
332
- uptime: process.uptime(),
333
- timestamp: new Date().toISOString(),
423
+ status: 'ok', // Indicate service is healthy
424
+ uptime: process.uptime(), // Server uptime in seconds
425
+ timestamp: new Date().toISOString(), // Current timestamp
334
426
  };
335
427
  res.status(200).json(healthStatus);
336
428
  });
429
+ // Endpoint to provide memory usage details
337
430
  this.expressApp.get('/memory', async (req, res) => {
338
431
  this.log.debug('Express received /memory');
432
+ // Memory usage from process
339
433
  const memoryUsageRaw = process.memoryUsage();
340
434
  const memoryUsage = {
341
435
  rss: formatBytes(memoryUsageRaw.rss),
@@ -344,10 +438,13 @@ export class Frontend extends EventEmitter {
344
438
  external: formatBytes(memoryUsageRaw.external),
345
439
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
346
440
  };
441
+ // V8 heap statistics
347
442
  const { default: v8 } = await import('node:v8');
348
443
  const heapStatsRaw = v8.getHeapStatistics();
349
444
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
445
+ // Format heapStats
350
446
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
447
+ // Format heapSpaces
351
448
  const heapSpaces = heapSpacesRaw.map((space) => ({
352
449
  ...space,
353
450
  space_size: formatBytes(space.space_size),
@@ -366,18 +463,22 @@ export class Frontend extends EventEmitter {
366
463
  };
367
464
  res.status(200).json(memoryReport);
368
465
  });
466
+ // Endpoint to provide settings
369
467
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
370
468
  this.log.debug('The frontend sent /api/settings');
371
469
  res.json(await this.getApiSettings());
372
470
  });
471
+ // Endpoint to provide plugins
373
472
  this.expressApp.get('/api/plugins', async (req, res) => {
374
473
  this.log.debug('The frontend sent /api/plugins');
375
474
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
376
475
  });
476
+ // Endpoint to provide devices
377
477
  this.expressApp.get('/api/devices', async (req, res) => {
378
478
  this.log.debug('The frontend sent /api/devices');
379
479
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
380
480
  });
481
+ // Endpoint to view the matterbridge log
381
482
  this.expressApp.get('/api/view-mblog', async (req, res) => {
382
483
  this.log.debug('The frontend sent /api/view-mblog');
383
484
  try {
@@ -391,6 +492,7 @@ export class Frontend extends EventEmitter {
391
492
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
392
493
  }
393
494
  });
495
+ // Endpoint to view the matter.js log
394
496
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
395
497
  this.log.debug('The frontend sent /api/view-mjlog');
396
498
  try {
@@ -404,6 +506,7 @@ export class Frontend extends EventEmitter {
404
506
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
405
507
  }
406
508
  });
509
+ // Endpoint to view the diagnostic.log
407
510
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
408
511
  this.log.debug('The frontend sent /api/view-diagnostic');
409
512
  await this.generateDiagnostic();
@@ -414,10 +517,13 @@ export class Frontend extends EventEmitter {
414
517
  res.send(data.slice(29));
415
518
  }
416
519
  catch (error) {
520
+ // istanbul ignore next
417
521
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
522
+ // istanbul ignore next
418
523
  res.status(500).send('Error reading diagnostic log file.');
419
524
  }
420
525
  });
526
+ // Endpoint to download the diagnostic.log
421
527
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
422
528
  this.log.debug(`The frontend sent /api/download-diagnostic`);
423
529
  await this.generateDiagnostic();
@@ -428,16 +534,19 @@ export class Frontend extends EventEmitter {
428
534
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
429
535
  }
430
536
  catch (error) {
537
+ // istanbul ignore next
431
538
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
432
539
  }
433
540
  res.type('text/plain');
434
541
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
542
+ /* istanbul ignore if */
435
543
  if (error) {
436
544
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
437
545
  res.status(500).send('Error downloading the diagnostic log file');
438
546
  }
439
547
  });
440
548
  });
549
+ // Endpoint to view the history.html
441
550
  this.expressApp.get('/api/viewhistory', async (req, res) => {
442
551
  this.log.debug('The frontend sent /api/viewhistory');
443
552
  try {
@@ -451,6 +560,7 @@ export class Frontend extends EventEmitter {
451
560
  res.status(500).send('Error reading history file.');
452
561
  }
453
562
  });
563
+ // Endpoint to download the history.html
454
564
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
455
565
  this.log.debug(`The frontend sent /api/downloadhistory`);
456
566
  try {
@@ -460,6 +570,7 @@ export class Frontend extends EventEmitter {
460
570
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
461
571
  res.type('text/plain');
462
572
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
573
+ /* istanbul ignore if */
463
574
  if (error) {
464
575
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
465
576
  res.status(500).send('Error downloading history file');
@@ -471,6 +582,7 @@ export class Frontend extends EventEmitter {
471
582
  res.status(500).send('Error reading history file.');
472
583
  }
473
584
  });
585
+ // Endpoint to view the shelly log
474
586
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
475
587
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
476
588
  try {
@@ -484,6 +596,7 @@ export class Frontend extends EventEmitter {
484
596
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
485
597
  }
486
598
  });
599
+ // Endpoint to download the matterbridge log
487
600
  this.expressApp.get('/api/download-mblog', async (req, res) => {
488
601
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
489
602
  const fs = await import('node:fs');
@@ -498,12 +611,14 @@ export class Frontend extends EventEmitter {
498
611
  }
499
612
  res.type('text/plain');
500
613
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
614
+ /* istanbul ignore if */
501
615
  if (error) {
502
616
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
503
617
  res.status(500).send('Error downloading the matterbridge log file');
504
618
  }
505
619
  });
506
620
  });
621
+ // Endpoint to download the matter log
507
622
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
508
623
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
509
624
  const fs = await import('node:fs');
@@ -518,12 +633,14 @@ export class Frontend extends EventEmitter {
518
633
  }
519
634
  res.type('text/plain');
520
635
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
636
+ /* istanbul ignore if */
521
637
  if (error) {
522
638
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
523
639
  res.status(500).send('Error downloading the matter log file');
524
640
  }
525
641
  });
526
642
  });
643
+ // Endpoint to download the shelly log
527
644
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
528
645
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
529
646
  const fs = await import('node:fs');
@@ -538,75 +655,91 @@ export class Frontend extends EventEmitter {
538
655
  }
539
656
  res.type('text/plain');
540
657
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
658
+ /* istanbul ignore if */
541
659
  if (error) {
542
660
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
543
661
  res.status(500).send('Error downloading Shelly system log file');
544
662
  }
545
663
  });
546
664
  });
665
+ // Endpoint to download the matterbridge storage directory
547
666
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
548
667
  this.log.debug('The frontend sent /api/download-mbstorage');
549
668
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
550
669
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
670
+ /* istanbul ignore if */
551
671
  if (error) {
552
672
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
553
673
  res.status(500).send('Error downloading the matterbridge storage file');
554
674
  }
555
675
  });
556
676
  });
677
+ // Endpoint to download the matter storage file
557
678
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
558
679
  this.log.debug('The frontend sent /api/download-mjstorage');
559
680
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
560
681
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
682
+ /* istanbul ignore if */
561
683
  if (error) {
562
684
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
563
685
  res.status(500).send('Error downloading the matter storage zip file');
564
686
  }
565
687
  });
566
688
  });
689
+ // Endpoint to download the matterbridge plugin directory
567
690
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
568
691
  this.log.debug('The frontend sent /api/download-pluginstorage');
569
692
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
570
693
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
694
+ /* istanbul ignore if */
571
695
  if (error) {
572
696
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
573
697
  res.status(500).send('Error downloading the matterbridge plugin storage file');
574
698
  }
575
699
  });
576
700
  });
701
+ // Endpoint to download the matterbridge plugin config files
577
702
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
578
703
  this.log.debug('The frontend sent /api/download-pluginconfig');
579
704
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
580
705
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
706
+ /* istanbul ignore if */
581
707
  if (error) {
582
708
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
583
709
  res.status(500).send('Error downloading the matterbridge plugin config file');
584
710
  }
585
711
  });
586
712
  });
713
+ // Endpoint to download the matterbridge backup (created with the backup command)
587
714
  this.expressApp.get('/api/download-backup', async (req, res) => {
588
715
  this.log.debug('The frontend sent /api/download-backup');
589
716
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
717
+ /* istanbul ignore if */
590
718
  if (error) {
591
719
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
592
720
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
593
721
  }
594
722
  });
595
723
  });
724
+ // Endpoint to upload a package
596
725
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
597
726
  const { filename } = req.body;
598
727
  const file = req.file;
728
+ /* istanbul ignore if */
599
729
  if (!file || !filename) {
600
730
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
601
731
  res.status(400).send('Invalid request: file and filename are required');
602
732
  return;
603
733
  }
604
734
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
735
+ // Define the path where the plugin file will be saved
605
736
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
606
737
  try {
738
+ // Move the uploaded file to the specified path
607
739
  const fs = await import('node:fs');
608
740
  await fs.promises.rename(file.path, filePath);
609
741
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
742
+ // Install the plugin package
610
743
  if (filename.endsWith('.tgz')) {
611
744
  const { spawnCommand } = await import('./utils/spawn.js');
612
745
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -626,6 +759,7 @@ export class Frontend extends EventEmitter {
626
759
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
627
760
  }
628
761
  });
762
+ // Fallback for routing (must be the last route)
629
763
  this.expressApp.use((req, res) => {
630
764
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
631
765
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -635,13 +769,16 @@ export class Frontend extends EventEmitter {
635
769
  async stop() {
636
770
  this.log.debug('Stopping the frontend...');
637
771
  const ws = await import('ws');
772
+ // Remove listeners from the express app
638
773
  if (this.expressApp) {
639
774
  this.expressApp.removeAllListeners();
640
775
  this.expressApp = undefined;
641
776
  this.log.debug('Frontend app closed successfully');
642
777
  }
778
+ // Close the WebSocket server
643
779
  if (this.webSocketServer) {
644
780
  this.log.debug('Closing WebSocket server...');
781
+ // Close all active connections
645
782
  this.webSocketServer.clients.forEach((client) => {
646
783
  if (client.readyState === ws.WebSocket.OPEN) {
647
784
  client.close();
@@ -650,6 +787,7 @@ export class Frontend extends EventEmitter {
650
787
  await withTimeout(new Promise((resolve) => {
651
788
  this.webSocketServer?.close((error) => {
652
789
  if (error) {
790
+ // istanbul ignore next
653
791
  this.log.error(`Error closing WebSocket server: ${error}`);
654
792
  }
655
793
  else {
@@ -662,8 +800,27 @@ export class Frontend extends EventEmitter {
662
800
  this.webSocketServer.removeAllListeners();
663
801
  this.webSocketServer = undefined;
664
802
  }
803
+ // Close the http server
665
804
  if (this.httpServer) {
666
805
  this.log.debug('Closing http server...');
806
+ /*
807
+ await withTimeout(
808
+ new Promise<void>((resolve) => {
809
+ this.httpServer?.close((error) => {
810
+ if (error) {
811
+ // istanbul ignore next
812
+ this.log.error(`Error closing http server: ${error}`);
813
+ } else {
814
+ this.log.debug('Http server closed successfully');
815
+ this.emit('server_stopped');
816
+ }
817
+ resolve();
818
+ });
819
+ }),
820
+ 5000,
821
+ false,
822
+ );
823
+ */
667
824
  this.httpServer.close();
668
825
  this.log.debug('Http server closed successfully');
669
826
  this.listening = false;
@@ -672,8 +829,27 @@ export class Frontend extends EventEmitter {
672
829
  this.httpServer = undefined;
673
830
  this.log.debug('Frontend http server closed successfully');
674
831
  }
832
+ // Close the https server
675
833
  if (this.httpsServer) {
676
834
  this.log.debug('Closing https server...');
835
+ /*
836
+ await withTimeout(
837
+ new Promise<void>((resolve) => {
838
+ this.httpsServer?.close((error) => {
839
+ if (error) {
840
+ // istanbul ignore next
841
+ this.log.error(`Error closing https server: ${error}`);
842
+ } else {
843
+ this.log.debug('Https server closed successfully');
844
+ this.emit('server_stopped');
845
+ }
846
+ resolve();
847
+ });
848
+ }),
849
+ 5000,
850
+ false,
851
+ );
852
+ */
677
853
  this.httpsServer.close();
678
854
  this.log.debug('Https server closed successfully');
679
855
  this.listening = false;
@@ -684,7 +860,13 @@ export class Frontend extends EventEmitter {
684
860
  }
685
861
  this.log.debug('Frontend stopped successfully');
686
862
  }
863
+ /**
864
+ * Retrieves the api settings data.
865
+ *
866
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
867
+ */
687
868
  async getApiSettings() {
869
+ // Update the variable system information properties
688
870
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
689
871
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
690
872
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -694,6 +876,7 @@ export class Frontend extends EventEmitter {
694
876
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
695
877
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
696
878
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
879
+ // Create the matterbridge information
697
880
  const info = {
698
881
  homeDirectory: this.matterbridge.homeDirectory,
699
882
  rootDirectory: this.matterbridge.rootDirectory,
@@ -729,9 +912,15 @@ export class Frontend extends EventEmitter {
729
912
  };
730
913
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
731
914
  }
915
+ /**
916
+ * Retrieves the reachable attribute.
917
+ *
918
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
919
+ * @returns {boolean} The reachable attribute.
920
+ */
732
921
  getReachability(device) {
733
922
  if (this.matterbridge.hasCleanupStarted)
734
- return false;
923
+ return false; // Skip if cleanup has started
735
924
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
736
925
  return false;
737
926
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -742,9 +931,15 @@ export class Frontend extends EventEmitter {
742
931
  return true;
743
932
  return false;
744
933
  }
934
+ /**
935
+ * Retrieves the power source attribute.
936
+ *
937
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
938
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
939
+ */
745
940
  getPowerSource(endpoint) {
746
941
  if (this.matterbridge.hasCleanupStarted)
747
- return;
942
+ return; // Skip if cleanup has started
748
943
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
749
944
  return undefined;
750
945
  const powerSource = (device) => {
@@ -759,16 +954,25 @@ export class Frontend extends EventEmitter {
759
954
  }
760
955
  return;
761
956
  };
957
+ // Root endpoint
762
958
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
763
959
  return powerSource(endpoint);
960
+ // Child endpoints
764
961
  for (const child of endpoint.getChildEndpoints()) {
765
962
  if (child.hasClusterServer(PowerSource.Cluster.id))
766
963
  return powerSource(child);
767
964
  }
768
965
  }
966
+ /**
967
+ * Retrieves the cluster text description from a given device.
968
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
969
+ *
970
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
971
+ * @returns {string} The attributes description of the cluster servers in the device.
972
+ */
769
973
  getClusterTextFromDevice(device) {
770
974
  if (this.matterbridge.hasCleanupStarted)
771
- return '';
975
+ return ''; // Skip if cleanup has started
772
976
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
773
977
  return '';
774
978
  const getUserLabel = (device) => {
@@ -778,6 +982,7 @@ export class Frontend extends EventEmitter {
778
982
  if (composed)
779
983
  return 'Composed: ' + composed.value;
780
984
  }
985
+ // istanbul ignore next cause is not reachable
781
986
  return '';
782
987
  };
783
988
  const getFixedLabel = (device) => {
@@ -787,11 +992,13 @@ export class Frontend extends EventEmitter {
787
992
  if (composed)
788
993
  return 'Composed: ' + composed.value;
789
994
  }
995
+ // istanbul ignore next cause is not reacheable
790
996
  return '';
791
997
  };
792
998
  let attributes = '';
793
999
  let supportedModes = [];
794
1000
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1001
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
795
1002
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
796
1003
  return;
797
1004
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -881,11 +1088,17 @@ export class Frontend extends EventEmitter {
881
1088
  if (clusterName === 'userLabel' && attributeName === 'labelList')
882
1089
  attributes += `${getUserLabel(device)} `;
883
1090
  });
1091
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
884
1092
  return attributes.trimStart().trimEnd();
885
1093
  }
1094
+ /**
1095
+ * Retrieves the registered plugins sanitized for res.json().
1096
+ *
1097
+ * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1098
+ */
886
1099
  getPlugins() {
887
1100
  if (this.matterbridge.hasCleanupStarted)
888
- return [];
1101
+ return []; // Skip if cleanup has started
889
1102
  const plugins = [];
890
1103
  for (const plugin of this.matterbridge.plugins.array()) {
891
1104
  plugins.push({
@@ -913,18 +1126,27 @@ export class Frontend extends EventEmitter {
913
1126
  schemaJson: plugin.schemaJson,
914
1127
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
915
1128
  hasBlackList: plugin.configJson?.blackList !== undefined,
1129
+ // Childbridge mode specific data
916
1130
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
917
1131
  });
918
1132
  }
919
1133
  return plugins;
920
1134
  }
1135
+ /**
1136
+ * Retrieves the devices from Matterbridge.
1137
+ *
1138
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1139
+ * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1140
+ */
921
1141
  getDevices(pluginName) {
922
1142
  if (this.matterbridge.hasCleanupStarted)
923
- return [];
1143
+ return []; // Skip if cleanup has started
924
1144
  const devices = [];
925
1145
  for (const device of this.matterbridge.devices.array()) {
1146
+ // Filter by pluginName if provided
926
1147
  if (pluginName && pluginName !== device.plugin)
927
1148
  continue;
1149
+ // Check if the device has the required properties
928
1150
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
929
1151
  continue;
930
1152
  devices.push({
@@ -944,24 +1166,39 @@ export class Frontend extends EventEmitter {
944
1166
  }
945
1167
  return devices;
946
1168
  }
1169
+ /**
1170
+ * Retrieves the clusters from a given plugin and endpoint number.
1171
+ *
1172
+ * Response for /api/clusters
1173
+ *
1174
+ * @param {string} pluginName - The name of the plugin.
1175
+ * @param {number} endpointNumber - The endpoint number.
1176
+ * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1177
+ */
947
1178
  getClusters(pluginName, endpointNumber) {
948
1179
  if (this.matterbridge.hasCleanupStarted)
949
- return;
1180
+ return; // Skip if cleanup has started
950
1181
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
951
1182
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
952
1183
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
953
1184
  return;
954
1185
  }
1186
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1187
+ // Get the device types from the main endpoint
955
1188
  const deviceTypes = [];
956
1189
  const clusters = [];
957
1190
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
958
1191
  deviceTypes.push(d.deviceType);
959
1192
  });
1193
+ // Get the clusters from the main endpoint
960
1194
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
961
1195
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
962
1196
  return;
963
1197
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
964
1198
  return;
1199
+ // console.log(
1200
+ // `${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}`,
1201
+ // );
965
1202
  clusters.push({
966
1203
  endpoint: endpoint.number.toString(),
967
1204
  number: endpoint.number,
@@ -975,12 +1212,19 @@ export class Frontend extends EventEmitter {
975
1212
  attributeLocalValue: attributeValue,
976
1213
  });
977
1214
  });
1215
+ // Get the child endpoints
978
1216
  const childEndpoints = endpoint.getChildEndpoints();
1217
+ // if (childEndpoints.length === 0) {
1218
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1219
+ // }
979
1220
  childEndpoints.forEach((childEndpoint) => {
1221
+ // istanbul ignore if cause is not reachable: should never happen but ...
980
1222
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
981
1223
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
982
1224
  return;
983
1225
  }
1226
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1227
+ // Get the device types of the child endpoint
984
1228
  const deviceTypes = [];
985
1229
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
986
1230
  deviceTypes.push(d.deviceType);
@@ -990,6 +1234,9 @@ export class Frontend extends EventEmitter {
990
1234
  return;
991
1235
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
992
1236
  return;
1237
+ // console.log(
1238
+ // `${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}`,
1239
+ // );
993
1240
  clusters.push({
994
1241
  endpoint: childEndpoint.number.toString(),
995
1242
  number: childEndpoint.number,
@@ -1009,6 +1256,7 @@ export class Frontend extends EventEmitter {
1009
1256
  async generateDiagnostic() {
1010
1257
  this.log.debug('Generating diagnostic...');
1011
1258
  const serverNodes = [];
1259
+ // istanbul ignore else
1012
1260
  if (this.matterbridge.bridgeMode === 'bridge') {
1013
1261
  if (this.matterbridge.serverNode)
1014
1262
  serverNodes.push(this.matterbridge.serverNode);
@@ -1019,6 +1267,7 @@ export class Frontend extends EventEmitter {
1019
1267
  serverNodes.push(plugin.serverNode);
1020
1268
  }
1021
1269
  }
1270
+ // istanbul ignore next
1022
1271
  for (const device of this.matterbridge.devices.array()) {
1023
1272
  if (device.serverNode)
1024
1273
  serverNodes.push(device.serverNode);
@@ -1042,8 +1291,15 @@ export class Frontend extends EventEmitter {
1042
1291
  values: [...serverNodes],
1043
1292
  })));
1044
1293
  delete Logger.destinations.diagnostic;
1045
- await wait(500);
1294
+ await wait(500); // Wait for the log to be written
1046
1295
  }
1296
+ /**
1297
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1298
+ *
1299
+ * @param {WebSocket} client - The websocket client that sent the message.
1300
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1301
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1302
+ */
1047
1303
  async wsMessageHandler(client, message) {
1048
1304
  let data;
1049
1305
  const sendResponse = (data) => {
@@ -1063,7 +1319,7 @@ export class Frontend extends EventEmitter {
1063
1319
  };
1064
1320
  try {
1065
1321
  data = JSON.parse(message.toString());
1066
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1322
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1067
1323
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1068
1324
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1069
1325
  return;
@@ -1137,6 +1393,7 @@ export class Frontend extends EventEmitter {
1137
1393
  return;
1138
1394
  })
1139
1395
  .catch((_error) => {
1396
+ //
1140
1397
  });
1141
1398
  }
1142
1399
  else {
@@ -1184,6 +1441,7 @@ export class Frontend extends EventEmitter {
1184
1441
  return;
1185
1442
  })
1186
1443
  .catch((_error) => {
1444
+ //
1187
1445
  });
1188
1446
  }
1189
1447
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1209,6 +1467,7 @@ export class Frontend extends EventEmitter {
1209
1467
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1210
1468
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1211
1469
  if (plugin.serverNode) {
1470
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1212
1471
  await this.matterbridge.stopServerNode(plugin.serverNode);
1213
1472
  plugin.serverNode = undefined;
1214
1473
  }
@@ -1218,18 +1477,20 @@ export class Frontend extends EventEmitter {
1218
1477
  this.matterbridge.devices.remove(device);
1219
1478
  }
1220
1479
  }
1480
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1221
1481
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1222
1482
  await this.matterbridge.createDynamicPlugin(plugin);
1223
1483
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1224
- plugin.restartRequired = false;
1484
+ plugin.restartRequired = false; // Reset plugin restartRequired
1225
1485
  let needRestart = 0;
1226
1486
  for (const plugin of this.matterbridge.plugins) {
1227
1487
  if (plugin.restartRequired)
1228
1488
  needRestart++;
1229
1489
  }
1230
1490
  if (needRestart === 0) {
1231
- this.wssSendRestartNotRequired(true);
1491
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1232
1492
  }
1493
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1233
1494
  if (plugin.serverNode)
1234
1495
  await this.matterbridge.startServerNode(plugin.serverNode);
1235
1496
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1494,22 +1755,22 @@ export class Frontend extends EventEmitter {
1494
1755
  if (isValidString(data.params.value, 4)) {
1495
1756
  this.log.debug('Matterbridge logger level:', data.params.value);
1496
1757
  if (data.params.value === 'Debug') {
1497
- await this.matterbridge.setLogLevel("debug");
1758
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1498
1759
  }
1499
1760
  else if (data.params.value === 'Info') {
1500
- await this.matterbridge.setLogLevel("info");
1761
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1501
1762
  }
1502
1763
  else if (data.params.value === 'Notice') {
1503
- await this.matterbridge.setLogLevel("notice");
1764
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1504
1765
  }
1505
1766
  else if (data.params.value === 'Warn') {
1506
- await this.matterbridge.setLogLevel("warn");
1767
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1507
1768
  }
1508
1769
  else if (data.params.value === 'Error') {
1509
- await this.matterbridge.setLogLevel("error");
1770
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1510
1771
  }
1511
1772
  else if (data.params.value === 'Fatal') {
1512
- await this.matterbridge.setLogLevel("fatal");
1773
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1513
1774
  }
1514
1775
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1515
1776
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1520,6 +1781,7 @@ export class Frontend extends EventEmitter {
1520
1781
  this.log.debug('Matterbridge file log:', data.params.value);
1521
1782
  this.matterbridge.fileLogger = data.params.value;
1522
1783
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1784
+ // Create the file logger for matterbridge
1523
1785
  if (data.params.value)
1524
1786
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1525
1787
  else
@@ -1548,11 +1810,12 @@ export class Frontend extends EventEmitter {
1548
1810
  else if (data.params.value === 'Fatal') {
1549
1811
  Logger.level = MatterLogLevel.FATAL;
1550
1812
  }
1551
- let callbackLogLevel = "notice";
1552
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1553
- callbackLogLevel = "info";
1554
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1555
- callbackLogLevel = "debug";
1813
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1814
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1815
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1816
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1817
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1818
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1556
1819
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1557
1820
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1558
1821
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -1604,6 +1867,7 @@ export class Frontend extends EventEmitter {
1604
1867
  }
1605
1868
  break;
1606
1869
  case 'setmatterport':
1870
+ // eslint-disable-next-line no-case-declarations
1607
1871
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1608
1872
  if (isValidNumber(port, 5540, 5600)) {
1609
1873
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1623,6 +1887,7 @@ export class Frontend extends EventEmitter {
1623
1887
  }
1624
1888
  break;
1625
1889
  case 'setmatterdiscriminator':
1890
+ // eslint-disable-next-line no-case-declarations
1626
1891
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1627
1892
  if (isValidNumber(discriminator, 0, 4095)) {
1628
1893
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1642,6 +1907,7 @@ export class Frontend extends EventEmitter {
1642
1907
  }
1643
1908
  break;
1644
1909
  case 'setmatterpasscode':
1910
+ // eslint-disable-next-line no-case-declarations
1645
1911
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1646
1912
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1647
1913
  this.matterbridge.passcode = passcode;
@@ -1687,15 +1953,19 @@ export class Frontend extends EventEmitter {
1687
1953
  return;
1688
1954
  }
1689
1955
  const config = plugin.configJson;
1956
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1690
1957
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1958
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1691
1959
  if (select === 'serial')
1692
1960
  this.log.info(`Selected device serial ${data.params.serial}`);
1693
1961
  if (select === 'name')
1694
1962
  this.log.info(`Selected device name ${data.params.name}`);
1695
1963
  if (config && select && (select === 'serial' || select === 'name')) {
1964
+ // Remove postfix from the serial if it exists
1696
1965
  if (config.postfix) {
1697
1966
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1698
1967
  }
1968
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1699
1969
  if (isValidArray(config.whiteList, 1)) {
1700
1970
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1701
1971
  config.whiteList.push(data.params.serial);
@@ -1704,6 +1974,7 @@ export class Frontend extends EventEmitter {
1704
1974
  config.whiteList.push(data.params.name);
1705
1975
  }
1706
1976
  }
1977
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1707
1978
  if (isValidArray(config.blackList, 1)) {
1708
1979
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1709
1980
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1731,7 +2002,9 @@ export class Frontend extends EventEmitter {
1731
2002
  return;
1732
2003
  }
1733
2004
  const config = plugin.configJson;
2005
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1734
2006
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2007
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1735
2008
  if (select === 'serial')
1736
2009
  this.log.info(`Unselected device serial ${data.params.serial}`);
1737
2010
  if (select === 'name')
@@ -1740,6 +2013,7 @@ export class Frontend extends EventEmitter {
1740
2013
  if (config.postfix) {
1741
2014
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1742
2015
  }
2016
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1743
2017
  if (isValidArray(config.whiteList, 1)) {
1744
2018
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1745
2019
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1748,6 +2022,7 @@ export class Frontend extends EventEmitter {
1748
2022
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1749
2023
  }
1750
2024
  }
2025
+ // Add the serial to the blackList
1751
2026
  if (isValidArray(config.blackList)) {
1752
2027
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1753
2028
  config.blackList.push(data.params.serial);
@@ -1770,6 +2045,7 @@ export class Frontend extends EventEmitter {
1770
2045
  }
1771
2046
  }
1772
2047
  else {
2048
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1773
2049
  const localData = data;
1774
2050
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1775
2051
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1779,23 +2055,46 @@ export class Frontend extends EventEmitter {
1779
2055
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1780
2056
  }
1781
2057
  }
2058
+ /**
2059
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2060
+ *
2061
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2062
+ * @param {string} time - The time string of the message
2063
+ * @param {string} name - The logger name of the message
2064
+ * @param {string} message - The content of the message.
2065
+ *
2066
+ * @remarks
2067
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2068
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2069
+ * The function sends the message to all connected clients.
2070
+ */
1782
2071
  wssSendLogMessage(level, time, name, message) {
1783
2072
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1784
2073
  return;
1785
2074
  if (!level || !time || !name || !message)
1786
2075
  return;
2076
+ // Remove ANSI escape codes from the message
2077
+ // eslint-disable-next-line no-control-regex
1787
2078
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2079
+ // Remove leading asterisks from the message
1788
2080
  message = message.replace(/^\*+/, '');
2081
+ // Replace all occurrences of \t and \n
1789
2082
  message = message.replace(/[\t\n]/g, '');
2083
+ // Remove non-printable characters
2084
+ // eslint-disable-next-line no-control-regex
1790
2085
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2086
+ // Replace all occurrences of \" with "
1791
2087
  message = message.replace(/\\"/g, '"');
2088
+ // Define the maximum allowed length for continuous characters without a space
1792
2089
  const maxContinuousLength = 100;
1793
2090
  const keepStartLength = 20;
1794
2091
  const keepEndLength = 20;
2092
+ // Split the message into words
1795
2093
  if (level !== 'spawn') {
1796
2094
  message = message
1797
2095
  .split(' ')
1798
2096
  .map((word) => {
2097
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1799
2098
  if (word.length > maxContinuousLength) {
1800
2099
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1801
2100
  }
@@ -1803,14 +2102,34 @@ export class Frontend extends EventEmitter {
1803
2102
  })
1804
2103
  .join(' ');
1805
2104
  }
2105
+ // Send the message to all connected clients
1806
2106
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
1807
2107
  }
2108
+ /**
2109
+ * Sends a need to refresh WebSocket message to all connected clients.
2110
+ *
2111
+ * @param {string} changed - The changed value.
2112
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2113
+ * possible values for changed:
2114
+ * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2115
+ * - 'plugins'
2116
+ * - 'devices'
2117
+ * - 'matter' with param 'matter' (QRDiv component)
2118
+ * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2119
+ */
1808
2120
  wssSendRefreshRequired(changed, params) {
1809
2121
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1810
2122
  return;
1811
2123
  this.log.debug('Sending a refresh required message to all connected clients');
2124
+ // Send the message to all connected clients
1812
2125
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
1813
2126
  }
2127
+ /**
2128
+ * Sends a need to restart WebSocket message to all connected clients.
2129
+ *
2130
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2131
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2132
+ */
1814
2133
  wssSendRestartRequired(snackbar = true, fixed = false) {
1815
2134
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1816
2135
  return;
@@ -1819,8 +2138,14 @@ export class Frontend extends EventEmitter {
1819
2138
  this.matterbridge.fixedRestartRequired = fixed;
1820
2139
  if (snackbar === true)
1821
2140
  this.wssSendSnackbarMessage(`Restart required`, 0);
2141
+ // Send the message to all connected clients
1822
2142
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1823
2143
  }
2144
+ /**
2145
+ * Sends a no need to restart WebSocket message to all connected clients.
2146
+ *
2147
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2148
+ */
1824
2149
  wssSendRestartNotRequired(snackbar = true) {
1825
2150
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1826
2151
  return;
@@ -1828,57 +2153,133 @@ export class Frontend extends EventEmitter {
1828
2153
  this.matterbridge.restartRequired = false;
1829
2154
  if (snackbar === true)
1830
2155
  this.wssSendCloseSnackbarMessage(`Restart required`);
2156
+ // Send the message to all connected clients
1831
2157
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1832
2158
  }
2159
+ /**
2160
+ * Sends a need to update WebSocket message to all connected clients.
2161
+ *
2162
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2163
+ */
1833
2164
  wssSendUpdateRequired(devVersion = false) {
1834
2165
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1835
2166
  return;
1836
2167
  this.log.debug('Sending an update required message to all connected clients');
1837
2168
  this.matterbridge.updateRequired = true;
2169
+ // Send the message to all connected clients
1838
2170
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1839
2171
  }
2172
+ /**
2173
+ * Sends a cpu update message to all connected clients.
2174
+ *
2175
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2176
+ * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2177
+ */
1840
2178
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
1841
2179
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1842
2180
  return;
1843
2181
  if (hasParameter('debug'))
1844
2182
  this.log.debug('Sending a cpu update message to all connected clients');
2183
+ // Send the message to all connected clients
1845
2184
  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 } });
1846
2185
  }
2186
+ /**
2187
+ * Sends a memory update message to all connected clients.
2188
+ *
2189
+ * @param {string} totalMemory - The total memory in bytes.
2190
+ * @param {string} freeMemory - The free memory in bytes.
2191
+ * @param {string} rss - The resident set size in bytes.
2192
+ * @param {string} heapTotal - The total heap memory in bytes.
2193
+ * @param {string} heapUsed - The used heap memory in bytes.
2194
+ * @param {string} external - The external memory in bytes.
2195
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2196
+ */
1847
2197
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1848
2198
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1849
2199
  return;
1850
2200
  if (hasParameter('debug'))
1851
2201
  this.log.debug('Sending a memory update message to all connected clients');
2202
+ // Send the message to all connected clients
1852
2203
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1853
2204
  }
2205
+ /**
2206
+ * Sends an uptime update message to all connected clients.
2207
+ *
2208
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2209
+ * @param {string} processUptime - The process uptime in a human-readable format.
2210
+ */
1854
2211
  wssSendUptimeUpdate(systemUptime, processUptime) {
1855
2212
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1856
2213
  return;
1857
2214
  if (hasParameter('debug'))
1858
2215
  this.log.debug('Sending a uptime update message to all connected clients');
2216
+ // Send the message to all connected clients
1859
2217
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
1860
2218
  }
2219
+ /**
2220
+ * Sends an open snackbar message to all connected clients.
2221
+ *
2222
+ * @param {string} message - The message to send.
2223
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2224
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2225
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2226
+ *
2227
+ * @remarks
2228
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2229
+ */
1861
2230
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1862
2231
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1863
2232
  return;
1864
2233
  this.log.debug('Sending a snackbar message to all connected clients');
2234
+ // Send the message to all connected clients
1865
2235
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
1866
2236
  }
2237
+ /**
2238
+ * Sends a close snackbar message to all connected clients.
2239
+ * It will close the snackbar message with the same message and timeout = 0.
2240
+ *
2241
+ * @param {string} message - The message to send.
2242
+ */
1867
2243
  wssSendCloseSnackbarMessage(message) {
1868
2244
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1869
2245
  return;
1870
2246
  this.log.debug('Sending a close snackbar message to all connected clients');
2247
+ // Send the message to all connected clients
1871
2248
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1872
2249
  }
2250
+ /**
2251
+ * Sends an attribute update message to all connected WebSocket clients.
2252
+ *
2253
+ * @param {string | undefined} plugin - The name of the plugin.
2254
+ * @param {string | undefined} serialNumber - The serial number of the device.
2255
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2256
+ * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2257
+ * @param {string} id - The endpoint id where the attribute belongs.
2258
+ * @param {string} cluster - The cluster name where the attribute belongs.
2259
+ * @param {string} attribute - The name of the attribute that changed.
2260
+ * @param {number | string | boolean} value - The new value of the attribute.
2261
+ *
2262
+ * @remarks
2263
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2264
+ * with the updated attribute information.
2265
+ */
1873
2266
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1874
2267
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1875
2268
  return;
1876
2269
  this.log.debug('Sending an attribute update message to all connected clients');
2270
+ // Send the message to all connected clients
1877
2271
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1878
2272
  }
2273
+ /**
2274
+ * Sends a message to all connected clients.
2275
+ * This is an helper function to send a broadcast message to all connected clients.
2276
+ *
2277
+ * @param {WsMessageBroadcast} msg - The message to send.
2278
+ */
1879
2279
  wssBroadcastMessage(msg) {
1880
2280
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1881
2281
  return;
2282
+ // Send the message to all connected clients
1882
2283
  const stringifiedMsg = JSON.stringify(msg);
1883
2284
  if (msg.method !== 'log')
1884
2285
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -1889,3 +2290,4 @@ export class Frontend extends EventEmitter {
1889
2290
  });
1890
2291
  }
1891
2292
  }
2293
+ //# sourceMappingURL=frontend.js.map