matterbridge 3.3.4 → 3.3.5-dev-20251028-d89f93f

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 (305) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/broadcastServer.js +1 -92
  3. package/dist/broadcastServerTypes.js +0 -24
  4. package/dist/cli.js +1 -97
  5. package/dist/cliEmitter.js +0 -37
  6. package/dist/cliHistory.js +0 -38
  7. package/dist/clusters/export.js +0 -2
  8. package/dist/defaultConfigSchema.js +0 -24
  9. package/dist/deviceManager.js +8 -124
  10. package/dist/devices/airConditioner.js +0 -57
  11. package/dist/devices/batteryStorage.js +1 -48
  12. package/dist/devices/cooktop.js +0 -55
  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 -42
  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 +165 -493
  36. package/dist/frontendTypes.js +0 -45
  37. package/dist/helpers.js +0 -53
  38. package/dist/index.js +0 -25
  39. package/dist/logger/export.js +0 -1
  40. package/dist/matter/behaviors.js +0 -2
  41. package/dist/matter/clusters.js +0 -2
  42. package/dist/matter/devices.js +0 -2
  43. package/dist/matter/endpoints.js +0 -2
  44. package/dist/matter/export.js +0 -3
  45. package/dist/matter/types.js +0 -3
  46. package/dist/matterbridge.js +53 -828
  47. package/dist/matterbridgeAccessoryPlatform.js +0 -37
  48. package/dist/matterbridgeBehaviors.js +5 -68
  49. package/dist/matterbridgeDeviceTypes.js +17 -638
  50. package/dist/matterbridgeDynamicPlatform.js +0 -37
  51. package/dist/matterbridgeEndpoint.js +52 -1402
  52. package/dist/matterbridgeEndpointHelpers.js +19 -464
  53. package/dist/matterbridgePlatform.js +1 -341
  54. package/dist/matterbridgeTypes.js +0 -26
  55. package/dist/pluginManager.js +11 -319
  56. package/dist/shelly.js +7 -168
  57. package/dist/storage/export.js +0 -1
  58. package/dist/update.js +0 -69
  59. package/dist/utils/colorUtils.js +2 -97
  60. package/dist/utils/commandLine.js +0 -60
  61. package/dist/utils/copyDirectory.js +1 -38
  62. package/dist/utils/createDirectory.js +0 -33
  63. package/dist/utils/createZip.js +2 -47
  64. package/dist/utils/deepCopy.js +0 -39
  65. package/dist/utils/deepEqual.js +1 -72
  66. package/dist/utils/error.js +0 -41
  67. package/dist/utils/export.js +0 -1
  68. package/dist/utils/format.js +0 -49
  69. package/dist/utils/hex.js +0 -124
  70. package/dist/utils/inspector.js +1 -69
  71. package/dist/utils/isvalid.js +0 -101
  72. package/dist/utils/jestHelpers.js +3 -153
  73. package/dist/utils/network.js +5 -96
  74. package/dist/utils/spawn.js +0 -71
  75. package/dist/utils/tracker.js +1 -64
  76. package/dist/utils/wait.js +8 -60
  77. package/frontend/build/assets/index.js +4 -4
  78. package/frontend/package.json +1 -1
  79. package/npm-shrinkwrap.json +2 -2
  80. package/package.json +1 -2
  81. package/dist/broadcastServer.d.ts +0 -112
  82. package/dist/broadcastServer.d.ts.map +0 -1
  83. package/dist/broadcastServer.js.map +0 -1
  84. package/dist/broadcastServerTypes.d.ts +0 -793
  85. package/dist/broadcastServerTypes.d.ts.map +0 -1
  86. package/dist/broadcastServerTypes.js.map +0 -1
  87. package/dist/cli.d.ts +0 -30
  88. package/dist/cli.d.ts.map +0 -1
  89. package/dist/cli.js.map +0 -1
  90. package/dist/cliEmitter.d.ts +0 -50
  91. package/dist/cliEmitter.d.ts.map +0 -1
  92. package/dist/cliEmitter.js.map +0 -1
  93. package/dist/cliHistory.d.ts +0 -48
  94. package/dist/cliHistory.d.ts.map +0 -1
  95. package/dist/cliHistory.js.map +0 -1
  96. package/dist/clusters/export.d.ts +0 -2
  97. package/dist/clusters/export.d.ts.map +0 -1
  98. package/dist/clusters/export.js.map +0 -1
  99. package/dist/defaultConfigSchema.d.ts +0 -28
  100. package/dist/defaultConfigSchema.d.ts.map +0 -1
  101. package/dist/defaultConfigSchema.js.map +0 -1
  102. package/dist/deviceManager.d.ts +0 -117
  103. package/dist/deviceManager.d.ts.map +0 -1
  104. package/dist/deviceManager.js.map +0 -1
  105. package/dist/devices/airConditioner.d.ts +0 -98
  106. package/dist/devices/airConditioner.d.ts.map +0 -1
  107. package/dist/devices/airConditioner.js.map +0 -1
  108. package/dist/devices/batteryStorage.d.ts +0 -48
  109. package/dist/devices/batteryStorage.d.ts.map +0 -1
  110. package/dist/devices/batteryStorage.js.map +0 -1
  111. package/dist/devices/cooktop.d.ts +0 -60
  112. package/dist/devices/cooktop.d.ts.map +0 -1
  113. package/dist/devices/cooktop.js.map +0 -1
  114. package/dist/devices/dishwasher.d.ts +0 -71
  115. package/dist/devices/dishwasher.d.ts.map +0 -1
  116. package/dist/devices/dishwasher.js.map +0 -1
  117. package/dist/devices/evse.d.ts +0 -76
  118. package/dist/devices/evse.d.ts.map +0 -1
  119. package/dist/devices/evse.js.map +0 -1
  120. package/dist/devices/export.d.ts +0 -17
  121. package/dist/devices/export.d.ts.map +0 -1
  122. package/dist/devices/export.js.map +0 -1
  123. package/dist/devices/extractorHood.d.ts +0 -46
  124. package/dist/devices/extractorHood.d.ts.map +0 -1
  125. package/dist/devices/extractorHood.js.map +0 -1
  126. package/dist/devices/heatPump.d.ts +0 -47
  127. package/dist/devices/heatPump.d.ts.map +0 -1
  128. package/dist/devices/heatPump.js.map +0 -1
  129. package/dist/devices/laundryDryer.d.ts +0 -67
  130. package/dist/devices/laundryDryer.d.ts.map +0 -1
  131. package/dist/devices/laundryDryer.js.map +0 -1
  132. package/dist/devices/laundryWasher.d.ts +0 -81
  133. package/dist/devices/laundryWasher.d.ts.map +0 -1
  134. package/dist/devices/laundryWasher.js.map +0 -1
  135. package/dist/devices/microwaveOven.d.ts +0 -168
  136. package/dist/devices/microwaveOven.d.ts.map +0 -1
  137. package/dist/devices/microwaveOven.js.map +0 -1
  138. package/dist/devices/oven.d.ts +0 -105
  139. package/dist/devices/oven.d.ts.map +0 -1
  140. package/dist/devices/oven.js.map +0 -1
  141. package/dist/devices/refrigerator.d.ts +0 -118
  142. package/dist/devices/refrigerator.d.ts.map +0 -1
  143. package/dist/devices/refrigerator.js.map +0 -1
  144. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  145. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  146. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  147. package/dist/devices/solarPower.d.ts +0 -40
  148. package/dist/devices/solarPower.d.ts.map +0 -1
  149. package/dist/devices/solarPower.js.map +0 -1
  150. package/dist/devices/speaker.d.ts +0 -87
  151. package/dist/devices/speaker.d.ts.map +0 -1
  152. package/dist/devices/speaker.js.map +0 -1
  153. package/dist/devices/temperatureControl.d.ts +0 -166
  154. package/dist/devices/temperatureControl.d.ts.map +0 -1
  155. package/dist/devices/temperatureControl.js.map +0 -1
  156. package/dist/devices/waterHeater.d.ts +0 -111
  157. package/dist/devices/waterHeater.d.ts.map +0 -1
  158. package/dist/devices/waterHeater.js.map +0 -1
  159. package/dist/dgram/coap.d.ts +0 -205
  160. package/dist/dgram/coap.d.ts.map +0 -1
  161. package/dist/dgram/coap.js.map +0 -1
  162. package/dist/dgram/dgram.d.ts +0 -141
  163. package/dist/dgram/dgram.d.ts.map +0 -1
  164. package/dist/dgram/dgram.js.map +0 -1
  165. package/dist/dgram/mb_coap.d.ts +0 -24
  166. package/dist/dgram/mb_coap.d.ts.map +0 -1
  167. package/dist/dgram/mb_coap.js.map +0 -1
  168. package/dist/dgram/mb_mdns.d.ts +0 -24
  169. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  170. package/dist/dgram/mb_mdns.js.map +0 -1
  171. package/dist/dgram/mdns.d.ts +0 -290
  172. package/dist/dgram/mdns.d.ts.map +0 -1
  173. package/dist/dgram/mdns.js.map +0 -1
  174. package/dist/dgram/multicast.d.ts +0 -67
  175. package/dist/dgram/multicast.d.ts.map +0 -1
  176. package/dist/dgram/multicast.js.map +0 -1
  177. package/dist/dgram/unicast.d.ts +0 -56
  178. package/dist/dgram/unicast.d.ts.map +0 -1
  179. package/dist/dgram/unicast.js.map +0 -1
  180. package/dist/frontend.d.ts +0 -235
  181. package/dist/frontend.d.ts.map +0 -1
  182. package/dist/frontend.js.map +0 -1
  183. package/dist/frontendTypes.d.ts +0 -529
  184. package/dist/frontendTypes.d.ts.map +0 -1
  185. package/dist/frontendTypes.js.map +0 -1
  186. package/dist/helpers.d.ts +0 -48
  187. package/dist/helpers.d.ts.map +0 -1
  188. package/dist/helpers.js.map +0 -1
  189. package/dist/index.d.ts +0 -33
  190. package/dist/index.d.ts.map +0 -1
  191. package/dist/index.js.map +0 -1
  192. package/dist/logger/export.d.ts +0 -2
  193. package/dist/logger/export.d.ts.map +0 -1
  194. package/dist/logger/export.js.map +0 -1
  195. package/dist/matter/behaviors.d.ts +0 -2
  196. package/dist/matter/behaviors.d.ts.map +0 -1
  197. package/dist/matter/behaviors.js.map +0 -1
  198. package/dist/matter/clusters.d.ts +0 -2
  199. package/dist/matter/clusters.d.ts.map +0 -1
  200. package/dist/matter/clusters.js.map +0 -1
  201. package/dist/matter/devices.d.ts +0 -2
  202. package/dist/matter/devices.d.ts.map +0 -1
  203. package/dist/matter/devices.js.map +0 -1
  204. package/dist/matter/endpoints.d.ts +0 -2
  205. package/dist/matter/endpoints.d.ts.map +0 -1
  206. package/dist/matter/endpoints.js.map +0 -1
  207. package/dist/matter/export.d.ts +0 -5
  208. package/dist/matter/export.d.ts.map +0 -1
  209. package/dist/matter/export.js.map +0 -1
  210. package/dist/matter/types.d.ts +0 -3
  211. package/dist/matter/types.d.ts.map +0 -1
  212. package/dist/matter/types.js.map +0 -1
  213. package/dist/matterbridge.d.ts +0 -475
  214. package/dist/matterbridge.d.ts.map +0 -1
  215. package/dist/matterbridge.js.map +0 -1
  216. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  217. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  218. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  219. package/dist/matterbridgeBehaviors.d.ts +0 -2404
  220. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  221. package/dist/matterbridgeBehaviors.js.map +0 -1
  222. package/dist/matterbridgeDeviceTypes.d.ts +0 -770
  223. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  224. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  225. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  226. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  227. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  228. package/dist/matterbridgeEndpoint.d.ts +0 -1550
  229. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  230. package/dist/matterbridgeEndpoint.js.map +0 -1
  231. package/dist/matterbridgeEndpointHelpers.d.ts +0 -758
  232. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  233. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  234. package/dist/matterbridgePlatform.d.ts +0 -402
  235. package/dist/matterbridgePlatform.d.ts.map +0 -1
  236. package/dist/matterbridgePlatform.js.map +0 -1
  237. package/dist/matterbridgeTypes.d.ts +0 -226
  238. package/dist/matterbridgeTypes.d.ts.map +0 -1
  239. package/dist/matterbridgeTypes.js.map +0 -1
  240. package/dist/pluginManager.d.ts +0 -347
  241. package/dist/pluginManager.d.ts.map +0 -1
  242. package/dist/pluginManager.js.map +0 -1
  243. package/dist/shelly.d.ts +0 -174
  244. package/dist/shelly.d.ts.map +0 -1
  245. package/dist/shelly.js.map +0 -1
  246. package/dist/storage/export.d.ts +0 -2
  247. package/dist/storage/export.d.ts.map +0 -1
  248. package/dist/storage/export.js.map +0 -1
  249. package/dist/update.d.ts +0 -75
  250. package/dist/update.d.ts.map +0 -1
  251. package/dist/update.js.map +0 -1
  252. package/dist/utils/colorUtils.d.ts +0 -99
  253. package/dist/utils/colorUtils.d.ts.map +0 -1
  254. package/dist/utils/colorUtils.js.map +0 -1
  255. package/dist/utils/commandLine.d.ts +0 -66
  256. package/dist/utils/commandLine.d.ts.map +0 -1
  257. package/dist/utils/commandLine.js.map +0 -1
  258. package/dist/utils/copyDirectory.d.ts +0 -33
  259. package/dist/utils/copyDirectory.d.ts.map +0 -1
  260. package/dist/utils/copyDirectory.js.map +0 -1
  261. package/dist/utils/createDirectory.d.ts +0 -34
  262. package/dist/utils/createDirectory.d.ts.map +0 -1
  263. package/dist/utils/createDirectory.js.map +0 -1
  264. package/dist/utils/createZip.d.ts +0 -39
  265. package/dist/utils/createZip.d.ts.map +0 -1
  266. package/dist/utils/createZip.js.map +0 -1
  267. package/dist/utils/deepCopy.d.ts +0 -32
  268. package/dist/utils/deepCopy.d.ts.map +0 -1
  269. package/dist/utils/deepCopy.js.map +0 -1
  270. package/dist/utils/deepEqual.d.ts +0 -54
  271. package/dist/utils/deepEqual.d.ts.map +0 -1
  272. package/dist/utils/deepEqual.js.map +0 -1
  273. package/dist/utils/error.d.ts +0 -44
  274. package/dist/utils/error.d.ts.map +0 -1
  275. package/dist/utils/error.js.map +0 -1
  276. package/dist/utils/export.d.ts +0 -13
  277. package/dist/utils/export.d.ts.map +0 -1
  278. package/dist/utils/export.js.map +0 -1
  279. package/dist/utils/format.d.ts +0 -53
  280. package/dist/utils/format.d.ts.map +0 -1
  281. package/dist/utils/format.js.map +0 -1
  282. package/dist/utils/hex.d.ts +0 -89
  283. package/dist/utils/hex.d.ts.map +0 -1
  284. package/dist/utils/hex.js.map +0 -1
  285. package/dist/utils/inspector.d.ts +0 -87
  286. package/dist/utils/inspector.d.ts.map +0 -1
  287. package/dist/utils/inspector.js.map +0 -1
  288. package/dist/utils/isvalid.d.ts +0 -103
  289. package/dist/utils/isvalid.d.ts.map +0 -1
  290. package/dist/utils/isvalid.js.map +0 -1
  291. package/dist/utils/jestHelpers.d.ts +0 -139
  292. package/dist/utils/jestHelpers.d.ts.map +0 -1
  293. package/dist/utils/jestHelpers.js.map +0 -1
  294. package/dist/utils/network.d.ts +0 -101
  295. package/dist/utils/network.d.ts.map +0 -1
  296. package/dist/utils/network.js.map +0 -1
  297. package/dist/utils/spawn.d.ts +0 -35
  298. package/dist/utils/spawn.d.ts.map +0 -1
  299. package/dist/utils/spawn.js.map +0 -1
  300. package/dist/utils/tracker.d.ts +0 -108
  301. package/dist/utils/tracker.d.ts.map +0 -1
  302. package/dist/utils/tracker.js.map +0 -1
  303. package/dist/utils/wait.d.ts +0 -54
  304. package/dist/utils/wait.d.ts.map +0 -1
  305. package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js CHANGED
@@ -1,34 +1,8 @@
1
- /**
2
- * This file contains the class Frontend.
3
- *
4
- * @file frontend.ts
5
- * @author Luca Liguori
6
- * @created 2025-01-13
7
- * @version 1.3.0
8
- * @license Apache-2.0
9
- *
10
- * Copyright 2025, 2026, 2027 Luca Liguori.
11
- *
12
- * Licensed under the Apache License, Version 2.0 (the "License");
13
- * you may not use this file except in compliance with the License.
14
- * You may obtain a copy of the License at
15
- *
16
- * http://www.apache.org/licenses/LICENSE-2.0
17
- *
18
- * Unless required by applicable law or agreed to in writing, software
19
- * distributed under the License is distributed on an "AS IS" BASIS,
20
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- * See the License for the specific language governing permissions and
22
- * limitations under the License.
23
- */
24
- // eslint-disable-next-line no-console
25
1
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
26
2
  console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
27
- // Node modules
28
3
  import os from 'node:os';
29
4
  import path from 'node:path';
30
5
  import EventEmitter from 'node:events';
31
- // AnsiLogger module
32
6
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
33
7
  import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
34
8
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
@@ -52,6 +26,7 @@ export class Frontend extends EventEmitter {
52
26
  log;
53
27
  port = 8283;
54
28
  listening = false;
29
+ storedPassword = undefined;
55
30
  expressApp;
56
31
  httpServer;
57
32
  httpsServer;
@@ -60,7 +35,7 @@ export class Frontend extends EventEmitter {
60
35
  constructor(matterbridge) {
61
36
  super();
62
37
  this.matterbridge = matterbridge;
63
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
38
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
64
39
  this.log.logNameColor = '\x1b[38;5;97m';
65
40
  this.server = new BroadcastServer('frontend', this.log);
66
41
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -72,6 +47,13 @@ export class Frontend extends EventEmitter {
72
47
  if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'frontend')) {
73
48
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
74
49
  switch (msg.type) {
50
+ case 'get_log_level':
51
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
52
+ break;
53
+ case 'set_log_level':
54
+ this.log.logLevel = msg.params.logLevel;
55
+ this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
56
+ break;
75
57
  case 'frontend_start':
76
58
  await this.start(msg.params.port);
77
59
  this.server.respond({ ...msg, response: { success: true } });
@@ -80,6 +62,30 @@ export class Frontend extends EventEmitter {
80
62
  await this.stop();
81
63
  this.server.respond({ ...msg, response: { success: true } });
82
64
  break;
65
+ case 'frontend_refreshrequired':
66
+ this.wssSendRefreshRequired(msg.params.changed, { matter: msg.params.matter });
67
+ this.server.respond({ ...msg, response: { success: true } });
68
+ break;
69
+ case 'frontend_restartrequired':
70
+ this.wssSendRestartRequired(msg.params.snackbar, msg.params.fixed);
71
+ this.server.respond({ ...msg, response: { success: true } });
72
+ break;
73
+ case 'frontend_restartnotrequired':
74
+ this.wssSendRestartNotRequired(msg.params.snackbar);
75
+ this.server.respond({ ...msg, response: { success: true } });
76
+ break;
77
+ case 'frontend_updaterequired':
78
+ this.wssSendUpdateRequired(msg.params.devVersion);
79
+ this.server.respond({ ...msg, response: { success: true } });
80
+ break;
81
+ case 'frontend_snackbarmessage':
82
+ this.wssSendSnackbarMessage(msg.params.message, msg.params.timeout, msg.params.severity);
83
+ this.server.respond({ ...msg, response: { success: true } });
84
+ break;
85
+ case 'frontend_attributechanged':
86
+ this.wssSendAttributeChangedMessage(msg.params.plugin, msg.params.serialNumber, msg.params.uniqueId, msg.params.number, msg.params.id, msg.params.cluster, msg.params.attribute, msg.params.value);
87
+ this.server.respond({ ...msg, response: { success: true } });
88
+ break;
83
89
  default:
84
90
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
85
91
  }
@@ -87,6 +93,9 @@ export class Frontend extends EventEmitter {
87
93
  if (this.server.isWorkerResponse(msg, msg.type)) {
88
94
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
89
95
  switch (msg.type) {
96
+ case 'get_log_level':
97
+ case 'set_log_level':
98
+ break;
90
99
  case 'plugins_install':
91
100
  this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
92
101
  if (msg.response.success) {
@@ -119,43 +128,56 @@ export class Frontend extends EventEmitter {
119
128
  }
120
129
  async start(port = 8283) {
121
130
  this.port = port;
131
+ this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
122
132
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
123
- // Initialize multer with the upload directory
124
133
  const multer = await import('multer');
125
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
134
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
126
135
  const upload = multer.default({ dest: uploadDir });
127
- // Create the express app that serves the frontend
128
136
  const express = await import('express');
129
137
  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
156
138
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
139
+ this.log.debug(`Creating WebSocketServer...`);
140
+ const ws = await import('ws');
141
+ this.webSocketServer = new ws.WebSocketServer({ noServer: true });
142
+ this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
143
+ this.webSocketServer.on('connection', (ws, request) => {
144
+ const clientIp = request.socket.remoteAddress;
145
+ let callbackLogLevel = "notice";
146
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
147
+ callbackLogLevel = "info";
148
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
149
+ callbackLogLevel = "debug";
150
+ AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
151
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
152
+ this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
153
+ ws.on('message', (message) => {
154
+ this.wsMessageHandler(ws, message);
155
+ });
156
+ ws.on('ping', () => {
157
+ this.log.debug('WebSocket client ping');
158
+ ws.pong();
159
+ });
160
+ ws.on('pong', () => {
161
+ this.log.debug('WebSocket client pong');
162
+ });
163
+ ws.on('close', () => {
164
+ this.log.info('WebSocket client disconnected');
165
+ if (this.webSocketServer?.clients.size === 0) {
166
+ AnsiLogger.setGlobalCallback(undefined);
167
+ this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
168
+ }
169
+ });
170
+ ws.on('error', (error) => {
171
+ this.log.error(`WebSocket client error: ${error}`);
172
+ });
173
+ });
174
+ this.webSocketServer.on('close', () => {
175
+ this.log.debug(`WebSocketServer closed`);
176
+ });
177
+ this.webSocketServer.on('error', (ws, error) => {
178
+ this.log.error(`WebSocketServer error: ${error}`);
179
+ });
157
180
  if (!hasParameter('ssl')) {
158
- // Create an HTTP server and attach the express app
159
181
  const http = await import('node:http');
160
182
  try {
161
183
  this.log.debug(`Creating HTTP server...`);
@@ -166,7 +188,6 @@ export class Frontend extends EventEmitter {
166
188
  this.emit('server_error', error);
167
189
  return;
168
190
  }
169
- // Listen on the specified port
170
191
  if (hasParameter('ingress')) {
171
192
  this.httpServer.listen(this.port, '0.0.0.0', () => {
172
193
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -184,6 +205,32 @@ export class Frontend extends EventEmitter {
184
205
  this.emit('server_listening', 'http', this.port);
185
206
  });
186
207
  }
208
+ this.httpServer.on('upgrade', async (req, socket, head) => {
209
+ try {
210
+ if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
211
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
212
+ return socket.destroy();
213
+ }
214
+ const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
215
+ const password = url.searchParams.get('password') ?? '';
216
+ if (password !== this.storedPassword) {
217
+ this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
218
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
219
+ return socket.destroy();
220
+ }
221
+ this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
222
+ this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
223
+ this.webSocketServer?.emit('connection', ws, req);
224
+ });
225
+ }
226
+ catch (err) {
227
+ {
228
+ inspectError(this.log, 'WebSocket upgrade error:', err);
229
+ socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
230
+ socket.destroy();
231
+ }
232
+ }
233
+ });
187
234
  this.httpServer.on('error', (error) => {
188
235
  this.log.error(`Frontend http server error listening on ${this.port}`);
189
236
  switch (error.code) {
@@ -199,7 +246,6 @@ export class Frontend extends EventEmitter {
199
246
  });
200
247
  }
201
248
  else {
202
- // SSL is enabled, load the certificate and the private key
203
249
  let cert;
204
250
  let key;
205
251
  let ca;
@@ -209,7 +255,6 @@ export class Frontend extends EventEmitter {
209
255
  let httpsServerOptions = {};
210
256
  const fs = await import('node:fs');
211
257
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
212
- // Load the p12 certificate and the passphrase
213
258
  try {
214
259
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
215
260
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -221,7 +266,7 @@ export class Frontend extends EventEmitter {
221
266
  }
222
267
  try {
223
268
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
224
- passphrase = passphrase.trim(); // Ensure no extra characters
269
+ passphrase = passphrase.trim();
225
270
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
226
271
  }
227
272
  catch (error) {
@@ -232,7 +277,6 @@ export class Frontend extends EventEmitter {
232
277
  httpsServerOptions = { pfx, passphrase };
233
278
  }
234
279
  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.
236
280
  try {
237
281
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
238
282
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -262,10 +306,9 @@ export class Frontend extends EventEmitter {
262
306
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
263
307
  }
264
308
  if (hasParameter('mtls')) {
265
- httpsServerOptions.requestCert = true; // Request client certificate
266
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
309
+ httpsServerOptions.requestCert = true;
310
+ httpsServerOptions.rejectUnauthorized = true;
267
311
  }
268
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
269
312
  const https = await import('node:https');
270
313
  try {
271
314
  this.log.debug(`Creating HTTPS server...`);
@@ -276,7 +319,6 @@ export class Frontend extends EventEmitter {
276
319
  this.emit('server_error', error);
277
320
  return;
278
321
  }
279
- // Listen on the specified port
280
322
  if (hasParameter('ingress')) {
281
323
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
282
324
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -294,6 +336,32 @@ export class Frontend extends EventEmitter {
294
336
  this.emit('server_listening', 'https', this.port);
295
337
  });
296
338
  }
339
+ this.httpsServer.on('upgrade', async (req, socket, head) => {
340
+ try {
341
+ if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
342
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
343
+ return socket.destroy();
344
+ }
345
+ const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
346
+ const password = url.searchParams.get('password') ?? '';
347
+ if (password !== this.storedPassword) {
348
+ this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
349
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
350
+ return socket.destroy();
351
+ }
352
+ this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
353
+ this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
354
+ this.webSocketServer?.emit('connection', ws, req);
355
+ });
356
+ }
357
+ catch (err) {
358
+ {
359
+ inspectError(this.log, 'WebSocket upgrade error:', err);
360
+ socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
361
+ socket.destroy();
362
+ }
363
+ }
364
+ });
297
365
  this.httpsServer.on('error', (error) => {
298
366
  this.log.error(`Frontend https server error listening on ${this.port}`);
299
367
  switch (error.code) {
@@ -308,77 +376,6 @@ export class Frontend extends EventEmitter {
308
376
  return;
309
377
  });
310
378
  }
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
323
- const ws = await import('ws');
324
- this.log.debug(`Creating WebSocketServer...`);
325
- this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
326
- this.webSocketServer.on('connection', (ws, request) => {
327
- const clientIp = request.socket.remoteAddress;
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 */;
346
- AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
347
- this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
348
- this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
349
- ws.on('message', (message) => {
350
- this.wsMessageHandler(ws, message);
351
- });
352
- ws.on('ping', () => {
353
- this.log.debug('WebSocket client ping');
354
- ws.pong();
355
- });
356
- ws.on('pong', () => {
357
- this.log.debug('WebSocket client pong');
358
- });
359
- ws.on('close', () => {
360
- this.log.info('WebSocket client disconnected');
361
- if (this.webSocketServer?.clients.size === 0) {
362
- AnsiLogger.setGlobalCallback(undefined);
363
- this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
364
- }
365
- });
366
- ws.on('error', (error) => {
367
- // istanbul ignore next
368
- this.log.error(`WebSocket client error: ${error}`);
369
- });
370
- });
371
- this.webSocketServer.on('close', () => {
372
- this.log.debug(`WebSocketServer closed`);
373
- });
374
- this.webSocketServer.on('listening', () => {
375
- this.log.info(`The WebSocketServer is listening`);
376
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
377
- });
378
- this.webSocketServer.on('error', (ws, error) => {
379
- this.log.error(`WebSocketServer error: ${error}`);
380
- });
381
- // Subscribe to cli events
382
379
  cliEmitter.removeAllListeners();
383
380
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
384
381
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -389,47 +386,29 @@ export class Frontend extends EventEmitter {
389
386
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
390
387
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
391
388
  });
392
- // Endpoint to validate login code
393
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
394
389
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
395
390
  const { password } = req.body;
396
- this.log.debug('The frontend sent /api/login', password);
397
- if (!this.matterbridge.nodeContext) {
398
- this.log.error('/api/login nodeContext not found');
399
- res.json({ valid: false });
400
- return;
401
- }
402
- try {
403
- const storedPassword = await this.matterbridge.nodeContext.get('password', '');
404
- if (storedPassword === '' || password === storedPassword) {
405
- this.log.debug('/api/login password valid');
406
- res.json({ valid: true });
407
- }
408
- else {
409
- this.log.warn('/api/login error wrong password');
410
- res.json({ valid: false });
411
- }
412
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
391
+ this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
392
+ if (this.storedPassword === '' || password === this.storedPassword) {
393
+ this.log.debug('/api/login password valid');
394
+ res.json({ valid: true });
413
395
  }
414
- catch (error) {
415
- this.log.error('/api/login error getting password');
396
+ else {
397
+ this.log.warn('/api/login error wrong password');
416
398
  res.json({ valid: false });
417
399
  }
418
400
  });
419
- // Endpoint to provide health check for docker
420
401
  this.expressApp.get('/health', (req, res) => {
421
402
  this.log.debug('Express received /health');
422
403
  const healthStatus = {
423
- status: 'ok', // Indicate service is healthy
424
- uptime: process.uptime(), // Server uptime in seconds
425
- timestamp: new Date().toISOString(), // Current timestamp
404
+ status: 'ok',
405
+ uptime: process.uptime(),
406
+ timestamp: new Date().toISOString(),
426
407
  };
427
408
  res.status(200).json(healthStatus);
428
409
  });
429
- // Endpoint to provide memory usage details
430
410
  this.expressApp.get('/memory', async (req, res) => {
431
411
  this.log.debug('Express received /memory');
432
- // Memory usage from process
433
412
  const memoryUsageRaw = process.memoryUsage();
434
413
  const memoryUsage = {
435
414
  rss: formatBytes(memoryUsageRaw.rss),
@@ -438,13 +417,10 @@ export class Frontend extends EventEmitter {
438
417
  external: formatBytes(memoryUsageRaw.external),
439
418
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
440
419
  };
441
- // V8 heap statistics
442
420
  const { default: v8 } = await import('node:v8');
443
421
  const heapStatsRaw = v8.getHeapStatistics();
444
422
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
445
- // Format heapStats
446
423
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
447
- // Format heapSpaces
448
424
  const heapSpaces = heapSpacesRaw.map((space) => ({
449
425
  ...space,
450
426
  space_size: formatBytes(space.space_size),
@@ -463,22 +439,18 @@ export class Frontend extends EventEmitter {
463
439
  };
464
440
  res.status(200).json(memoryReport);
465
441
  });
466
- // Endpoint to provide settings
467
442
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
468
443
  this.log.debug('The frontend sent /api/settings');
469
444
  res.json(await this.getApiSettings());
470
445
  });
471
- // Endpoint to provide plugins
472
446
  this.expressApp.get('/api/plugins', async (req, res) => {
473
447
  this.log.debug('The frontend sent /api/plugins');
474
448
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
475
449
  });
476
- // Endpoint to provide devices
477
450
  this.expressApp.get('/api/devices', async (req, res) => {
478
451
  this.log.debug('The frontend sent /api/devices');
479
452
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
480
453
  });
481
- // Endpoint to view the matterbridge log
482
454
  this.expressApp.get('/api/view-mblog', async (req, res) => {
483
455
  this.log.debug('The frontend sent /api/view-mblog');
484
456
  try {
@@ -492,7 +464,6 @@ export class Frontend extends EventEmitter {
492
464
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
493
465
  }
494
466
  });
495
- // Endpoint to view the matter.js log
496
467
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
497
468
  this.log.debug('The frontend sent /api/view-mjlog');
498
469
  try {
@@ -506,7 +477,6 @@ export class Frontend extends EventEmitter {
506
477
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
507
478
  }
508
479
  });
509
- // Endpoint to view the diagnostic.log
510
480
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
511
481
  this.log.debug('The frontend sent /api/view-diagnostic');
512
482
  await this.generateDiagnostic();
@@ -517,13 +487,10 @@ export class Frontend extends EventEmitter {
517
487
  res.send(data.slice(29));
518
488
  }
519
489
  catch (error) {
520
- // istanbul ignore next
521
490
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
522
- // istanbul ignore next
523
491
  res.status(500).send('Error reading diagnostic log file.');
524
492
  }
525
493
  });
526
- // Endpoint to download the diagnostic.log
527
494
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
528
495
  this.log.debug(`The frontend sent /api/download-diagnostic`);
529
496
  await this.generateDiagnostic();
@@ -534,19 +501,16 @@ export class Frontend extends EventEmitter {
534
501
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
535
502
  }
536
503
  catch (error) {
537
- // istanbul ignore next
538
504
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
539
505
  }
540
506
  res.type('text/plain');
541
507
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
542
- /* istanbul ignore if */
543
508
  if (error) {
544
509
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
545
510
  res.status(500).send('Error downloading the diagnostic log file');
546
511
  }
547
512
  });
548
513
  });
549
- // Endpoint to view the history.html
550
514
  this.expressApp.get('/api/viewhistory', async (req, res) => {
551
515
  this.log.debug('The frontend sent /api/viewhistory');
552
516
  try {
@@ -560,7 +524,6 @@ export class Frontend extends EventEmitter {
560
524
  res.status(500).send('Error reading history file.');
561
525
  }
562
526
  });
563
- // Endpoint to download the history.html
564
527
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
565
528
  this.log.debug(`The frontend sent /api/downloadhistory`);
566
529
  try {
@@ -570,7 +533,6 @@ export class Frontend extends EventEmitter {
570
533
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
571
534
  res.type('text/plain');
572
535
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
573
- /* istanbul ignore if */
574
536
  if (error) {
575
537
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
576
538
  res.status(500).send('Error downloading history file');
@@ -582,7 +544,6 @@ export class Frontend extends EventEmitter {
582
544
  res.status(500).send('Error reading history file.');
583
545
  }
584
546
  });
585
- // Endpoint to view the shelly log
586
547
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
587
548
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
588
549
  try {
@@ -596,7 +557,6 @@ export class Frontend extends EventEmitter {
596
557
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
597
558
  }
598
559
  });
599
- // Endpoint to download the matterbridge log
600
560
  this.expressApp.get('/api/download-mblog', async (req, res) => {
601
561
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
602
562
  const fs = await import('node:fs');
@@ -611,14 +571,12 @@ export class Frontend extends EventEmitter {
611
571
  }
612
572
  res.type('text/plain');
613
573
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
614
- /* istanbul ignore if */
615
574
  if (error) {
616
575
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
617
576
  res.status(500).send('Error downloading the matterbridge log file');
618
577
  }
619
578
  });
620
579
  });
621
- // Endpoint to download the matter log
622
580
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
623
581
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
624
582
  const fs = await import('node:fs');
@@ -633,14 +591,12 @@ export class Frontend extends EventEmitter {
633
591
  }
634
592
  res.type('text/plain');
635
593
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
636
- /* istanbul ignore if */
637
594
  if (error) {
638
595
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
639
596
  res.status(500).send('Error downloading the matter log file');
640
597
  }
641
598
  });
642
599
  });
643
- // Endpoint to download the shelly log
644
600
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
645
601
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
646
602
  const fs = await import('node:fs');
@@ -655,91 +611,75 @@ export class Frontend extends EventEmitter {
655
611
  }
656
612
  res.type('text/plain');
657
613
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
658
- /* istanbul ignore if */
659
614
  if (error) {
660
615
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
661
616
  res.status(500).send('Error downloading Shelly system log file');
662
617
  }
663
618
  });
664
619
  });
665
- // Endpoint to download the matterbridge storage directory
666
620
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
667
621
  this.log.debug('The frontend sent /api/download-mbstorage');
668
622
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
669
623
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
670
- /* istanbul ignore if */
671
624
  if (error) {
672
625
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
673
626
  res.status(500).send('Error downloading the matterbridge storage file');
674
627
  }
675
628
  });
676
629
  });
677
- // Endpoint to download the matter storage file
678
630
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
679
631
  this.log.debug('The frontend sent /api/download-mjstorage');
680
632
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
681
633
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
682
- /* istanbul ignore if */
683
634
  if (error) {
684
635
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
685
636
  res.status(500).send('Error downloading the matter storage zip file');
686
637
  }
687
638
  });
688
639
  });
689
- // Endpoint to download the matterbridge plugin directory
690
640
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
691
641
  this.log.debug('The frontend sent /api/download-pluginstorage');
692
642
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
693
643
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
694
- /* istanbul ignore if */
695
644
  if (error) {
696
645
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
697
646
  res.status(500).send('Error downloading the matterbridge plugin storage file');
698
647
  }
699
648
  });
700
649
  });
701
- // Endpoint to download the matterbridge plugin config files
702
650
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
703
651
  this.log.debug('The frontend sent /api/download-pluginconfig');
704
652
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
705
653
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
706
- /* istanbul ignore if */
707
654
  if (error) {
708
655
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
709
656
  res.status(500).send('Error downloading the matterbridge plugin config file');
710
657
  }
711
658
  });
712
659
  });
713
- // Endpoint to download the matterbridge backup (created with the backup command)
714
660
  this.expressApp.get('/api/download-backup', async (req, res) => {
715
661
  this.log.debug('The frontend sent /api/download-backup');
716
662
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
717
- /* istanbul ignore if */
718
663
  if (error) {
719
664
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
720
665
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
721
666
  }
722
667
  });
723
668
  });
724
- // Endpoint to upload a package
725
669
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
726
670
  const { filename } = req.body;
727
671
  const file = req.file;
728
- /* istanbul ignore if */
729
672
  if (!file || !filename) {
730
673
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
731
674
  res.status(400).send('Invalid request: file and filename are required');
732
675
  return;
733
676
  }
734
677
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
735
- // Define the path where the plugin file will be saved
736
678
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
737
679
  try {
738
- // Move the uploaded file to the specified path
739
680
  const fs = await import('node:fs');
740
681
  await fs.promises.rename(file.path, filePath);
741
682
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
742
- // Install the plugin package
743
683
  if (filename.endsWith('.tgz')) {
744
684
  const { spawnCommand } = await import('./utils/spawn.js');
745
685
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -759,7 +699,6 @@ export class Frontend extends EventEmitter {
759
699
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
760
700
  }
761
701
  });
762
- // Fallback for routing (must be the last route)
763
702
  this.expressApp.use((req, res) => {
764
703
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
765
704
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -769,16 +708,13 @@ export class Frontend extends EventEmitter {
769
708
  async stop() {
770
709
  this.log.debug('Stopping the frontend...');
771
710
  const ws = await import('ws');
772
- // Remove listeners from the express app
773
711
  if (this.expressApp) {
774
712
  this.expressApp.removeAllListeners();
775
713
  this.expressApp = undefined;
776
714
  this.log.debug('Frontend app closed successfully');
777
715
  }
778
- // Close the WebSocket server
779
716
  if (this.webSocketServer) {
780
717
  this.log.debug('Closing WebSocket server...');
781
- // Close all active connections
782
718
  this.webSocketServer.clients.forEach((client) => {
783
719
  if (client.readyState === ws.WebSocket.OPEN) {
784
720
  client.close();
@@ -787,7 +723,6 @@ export class Frontend extends EventEmitter {
787
723
  await withTimeout(new Promise((resolve) => {
788
724
  this.webSocketServer?.close((error) => {
789
725
  if (error) {
790
- // istanbul ignore next
791
726
  this.log.error(`Error closing WebSocket server: ${error}`);
792
727
  }
793
728
  else {
@@ -800,27 +735,8 @@ export class Frontend extends EventEmitter {
800
735
  this.webSocketServer.removeAllListeners();
801
736
  this.webSocketServer = undefined;
802
737
  }
803
- // Close the http server
804
738
  if (this.httpServer) {
805
739
  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
- */
824
740
  this.httpServer.close();
825
741
  this.log.debug('Http server closed successfully');
826
742
  this.listening = false;
@@ -829,27 +745,8 @@ export class Frontend extends EventEmitter {
829
745
  this.httpServer = undefined;
830
746
  this.log.debug('Frontend http server closed successfully');
831
747
  }
832
- // Close the https server
833
748
  if (this.httpsServer) {
834
749
  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
- */
853
750
  this.httpsServer.close();
854
751
  this.log.debug('Https server closed successfully');
855
752
  this.listening = false;
@@ -860,13 +757,7 @@ export class Frontend extends EventEmitter {
860
757
  }
861
758
  this.log.debug('Frontend stopped successfully');
862
759
  }
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
- */
868
760
  async getApiSettings() {
869
- // Update the variable system information properties
870
761
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
871
762
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
872
763
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -876,7 +767,6 @@ export class Frontend extends EventEmitter {
876
767
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
877
768
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
878
769
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
879
- // Create the matterbridge information
880
770
  const info = {
881
771
  homeDirectory: this.matterbridge.homeDirectory,
882
772
  rootDirectory: this.matterbridge.rootDirectory,
@@ -912,15 +802,9 @@ export class Frontend extends EventEmitter {
912
802
  };
913
803
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
914
804
  }
915
- /**
916
- * Retrieves the reachable attribute.
917
- *
918
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
919
- * @returns {boolean} The reachable attribute.
920
- */
921
805
  getReachability(device) {
922
806
  if (this.matterbridge.hasCleanupStarted)
923
- return false; // Skip if cleanup has started
807
+ return false;
924
808
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
925
809
  return false;
926
810
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -931,15 +815,9 @@ export class Frontend extends EventEmitter {
931
815
  return true;
932
816
  return false;
933
817
  }
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
- */
940
818
  getPowerSource(endpoint) {
941
819
  if (this.matterbridge.hasCleanupStarted)
942
- return; // Skip if cleanup has started
820
+ return;
943
821
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
944
822
  return undefined;
945
823
  const powerSource = (device) => {
@@ -954,25 +832,16 @@ export class Frontend extends EventEmitter {
954
832
  }
955
833
  return;
956
834
  };
957
- // Root endpoint
958
835
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
959
836
  return powerSource(endpoint);
960
- // Child endpoints
961
837
  for (const child of endpoint.getChildEndpoints()) {
962
838
  if (child.hasClusterServer(PowerSource.Cluster.id))
963
839
  return powerSource(child);
964
840
  }
965
841
  }
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
- */
973
842
  getClusterTextFromDevice(device) {
974
843
  if (this.matterbridge.hasCleanupStarted)
975
- return ''; // Skip if cleanup has started
844
+ return '';
976
845
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
977
846
  return '';
978
847
  const getUserLabel = (device) => {
@@ -982,7 +851,6 @@ export class Frontend extends EventEmitter {
982
851
  if (composed)
983
852
  return 'Composed: ' + composed.value;
984
853
  }
985
- // istanbul ignore next cause is not reachable
986
854
  return '';
987
855
  };
988
856
  const getFixedLabel = (device) => {
@@ -992,13 +860,11 @@ export class Frontend extends EventEmitter {
992
860
  if (composed)
993
861
  return 'Composed: ' + composed.value;
994
862
  }
995
- // istanbul ignore next cause is not reacheable
996
863
  return '';
997
864
  };
998
865
  let attributes = '';
999
866
  let supportedModes = [];
1000
867
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1001
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1002
868
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1003
869
  return;
1004
870
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1088,17 +954,11 @@ export class Frontend extends EventEmitter {
1088
954
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1089
955
  attributes += `${getUserLabel(device)} `;
1090
956
  });
1091
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1092
957
  return attributes.trimStart().trimEnd();
1093
958
  }
1094
- /**
1095
- * Retrieves the registered plugins sanitized for res.json().
1096
- *
1097
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1098
- */
1099
959
  getPlugins() {
1100
960
  if (this.matterbridge.hasCleanupStarted)
1101
- return []; // Skip if cleanup has started
961
+ return [];
1102
962
  const plugins = [];
1103
963
  for (const plugin of this.matterbridge.plugins.array()) {
1104
964
  plugins.push({
@@ -1126,27 +986,18 @@ export class Frontend extends EventEmitter {
1126
986
  schemaJson: plugin.schemaJson,
1127
987
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1128
988
  hasBlackList: plugin.configJson?.blackList !== undefined,
1129
- // Childbridge mode specific data
1130
989
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1131
990
  });
1132
991
  }
1133
992
  return plugins;
1134
993
  }
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
- */
1141
994
  getDevices(pluginName) {
1142
995
  if (this.matterbridge.hasCleanupStarted)
1143
- return []; // Skip if cleanup has started
996
+ return [];
1144
997
  const devices = [];
1145
998
  for (const device of this.matterbridge.devices.array()) {
1146
- // Filter by pluginName if provided
1147
999
  if (pluginName && pluginName !== device.plugin)
1148
1000
  continue;
1149
- // Check if the device has the required properties
1150
1001
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1151
1002
  continue;
1152
1003
  devices.push({
@@ -1166,39 +1017,24 @@ export class Frontend extends EventEmitter {
1166
1017
  }
1167
1018
  return devices;
1168
1019
  }
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
- */
1178
1020
  getClusters(pluginName, endpointNumber) {
1179
1021
  if (this.matterbridge.hasCleanupStarted)
1180
- return; // Skip if cleanup has started
1022
+ return;
1181
1023
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1182
1024
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1183
1025
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1184
1026
  return;
1185
1027
  }
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
1188
1028
  const deviceTypes = [];
1189
1029
  const clusters = [];
1190
1030
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1191
1031
  deviceTypes.push(d.deviceType);
1192
1032
  });
1193
- // Get the clusters from the main endpoint
1194
1033
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1195
1034
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1196
1035
  return;
1197
1036
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1198
1037
  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
- // );
1202
1038
  clusters.push({
1203
1039
  endpoint: endpoint.number.toString(),
1204
1040
  number: endpoint.number,
@@ -1212,19 +1048,12 @@ export class Frontend extends EventEmitter {
1212
1048
  attributeLocalValue: attributeValue,
1213
1049
  });
1214
1050
  });
1215
- // Get the child endpoints
1216
1051
  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
- // }
1220
1052
  childEndpoints.forEach((childEndpoint) => {
1221
- // istanbul ignore if cause is not reachable: should never happen but ...
1222
1053
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1223
1054
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1224
1055
  return;
1225
1056
  }
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
1228
1057
  const deviceTypes = [];
1229
1058
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1230
1059
  deviceTypes.push(d.deviceType);
@@ -1234,9 +1063,6 @@ export class Frontend extends EventEmitter {
1234
1063
  return;
1235
1064
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1236
1065
  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
- // );
1240
1066
  clusters.push({
1241
1067
  endpoint: childEndpoint.number.toString(),
1242
1068
  number: childEndpoint.number,
@@ -1256,7 +1082,6 @@ export class Frontend extends EventEmitter {
1256
1082
  async generateDiagnostic() {
1257
1083
  this.log.debug('Generating diagnostic...');
1258
1084
  const serverNodes = [];
1259
- // istanbul ignore else
1260
1085
  if (this.matterbridge.bridgeMode === 'bridge') {
1261
1086
  if (this.matterbridge.serverNode)
1262
1087
  serverNodes.push(this.matterbridge.serverNode);
@@ -1267,7 +1092,6 @@ export class Frontend extends EventEmitter {
1267
1092
  serverNodes.push(plugin.serverNode);
1268
1093
  }
1269
1094
  }
1270
- // istanbul ignore next
1271
1095
  for (const device of this.matterbridge.devices.array()) {
1272
1096
  if (device.serverNode)
1273
1097
  serverNodes.push(device.serverNode);
@@ -1291,15 +1115,8 @@ export class Frontend extends EventEmitter {
1291
1115
  values: [...serverNodes],
1292
1116
  })));
1293
1117
  delete Logger.destinations.diagnostic;
1294
- await wait(500); // Wait for the log to be written
1118
+ await wait(500);
1295
1119
  }
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
- */
1303
1120
  async wsMessageHandler(client, message) {
1304
1121
  let data;
1305
1122
  const sendResponse = (data) => {
@@ -1319,7 +1136,7 @@ export class Frontend extends EventEmitter {
1319
1136
  };
1320
1137
  try {
1321
1138
  data = JSON.parse(message.toString());
1322
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1139
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1323
1140
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1324
1141
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1325
1142
  return;
@@ -1393,7 +1210,6 @@ export class Frontend extends EventEmitter {
1393
1210
  return;
1394
1211
  })
1395
1212
  .catch((_error) => {
1396
- //
1397
1213
  });
1398
1214
  }
1399
1215
  else {
@@ -1441,7 +1257,6 @@ export class Frontend extends EventEmitter {
1441
1257
  return;
1442
1258
  })
1443
1259
  .catch((_error) => {
1444
- //
1445
1260
  });
1446
1261
  }
1447
1262
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1467,7 +1282,6 @@ export class Frontend extends EventEmitter {
1467
1282
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1468
1283
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1469
1284
  if (plugin.serverNode) {
1470
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1471
1285
  await this.matterbridge.stopServerNode(plugin.serverNode);
1472
1286
  plugin.serverNode = undefined;
1473
1287
  }
@@ -1477,20 +1291,18 @@ export class Frontend extends EventEmitter {
1477
1291
  this.matterbridge.devices.remove(device);
1478
1292
  }
1479
1293
  }
1480
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1481
1294
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1482
1295
  await this.matterbridge.createDynamicPlugin(plugin);
1483
1296
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1484
- plugin.restartRequired = false; // Reset plugin restartRequired
1297
+ plugin.restartRequired = false;
1485
1298
  let needRestart = 0;
1486
1299
  for (const plugin of this.matterbridge.plugins) {
1487
1300
  if (plugin.restartRequired)
1488
1301
  needRestart++;
1489
1302
  }
1490
1303
  if (needRestart === 0) {
1491
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1304
+ this.wssSendRestartNotRequired(true);
1492
1305
  }
1493
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1494
1306
  if (plugin.serverNode)
1495
1307
  await this.matterbridge.startServerNode(plugin.serverNode);
1496
1308
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1741,6 +1553,7 @@ export class Frontend extends EventEmitter {
1741
1553
  case 'setpassword':
1742
1554
  if (isValidString(data.params.value)) {
1743
1555
  await this.matterbridge.nodeContext?.set('password', data.params.value);
1556
+ this.storedPassword = data.params.value;
1744
1557
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1745
1558
  }
1746
1559
  break;
@@ -1755,22 +1568,22 @@ export class Frontend extends EventEmitter {
1755
1568
  if (isValidString(data.params.value, 4)) {
1756
1569
  this.log.debug('Matterbridge logger level:', data.params.value);
1757
1570
  if (data.params.value === 'Debug') {
1758
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1571
+ await this.matterbridge.setLogLevel("debug");
1759
1572
  }
1760
1573
  else if (data.params.value === 'Info') {
1761
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1574
+ await this.matterbridge.setLogLevel("info");
1762
1575
  }
1763
1576
  else if (data.params.value === 'Notice') {
1764
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1577
+ await this.matterbridge.setLogLevel("notice");
1765
1578
  }
1766
1579
  else if (data.params.value === 'Warn') {
1767
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1580
+ await this.matterbridge.setLogLevel("warn");
1768
1581
  }
1769
1582
  else if (data.params.value === 'Error') {
1770
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1583
+ await this.matterbridge.setLogLevel("error");
1771
1584
  }
1772
1585
  else if (data.params.value === 'Fatal') {
1773
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1586
+ await this.matterbridge.setLogLevel("fatal");
1774
1587
  }
1775
1588
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1776
1589
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1781,7 +1594,6 @@ export class Frontend extends EventEmitter {
1781
1594
  this.log.debug('Matterbridge file log:', data.params.value);
1782
1595
  this.matterbridge.fileLogger = data.params.value;
1783
1596
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1784
- // Create the file logger for matterbridge
1785
1597
  if (data.params.value)
1786
1598
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1787
1599
  else
@@ -1810,12 +1622,11 @@ export class Frontend extends EventEmitter {
1810
1622
  else if (data.params.value === 'Fatal') {
1811
1623
  Logger.level = MatterLogLevel.FATAL;
1812
1624
  }
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 */;
1625
+ let callbackLogLevel = "notice";
1626
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1627
+ callbackLogLevel = "info";
1628
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1629
+ callbackLogLevel = "debug";
1819
1630
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1820
1631
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1821
1632
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -1867,7 +1678,6 @@ export class Frontend extends EventEmitter {
1867
1678
  }
1868
1679
  break;
1869
1680
  case 'setmatterport':
1870
- // eslint-disable-next-line no-case-declarations
1871
1681
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1872
1682
  if (isValidNumber(port, 5540, 5600)) {
1873
1683
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1887,7 +1697,6 @@ export class Frontend extends EventEmitter {
1887
1697
  }
1888
1698
  break;
1889
1699
  case 'setmatterdiscriminator':
1890
- // eslint-disable-next-line no-case-declarations
1891
1700
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1892
1701
  if (isValidNumber(discriminator, 0, 4095)) {
1893
1702
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1907,7 +1716,6 @@ export class Frontend extends EventEmitter {
1907
1716
  }
1908
1717
  break;
1909
1718
  case 'setmatterpasscode':
1910
- // eslint-disable-next-line no-case-declarations
1911
1719
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1912
1720
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1913
1721
  this.matterbridge.passcode = passcode;
@@ -1953,19 +1761,15 @@ export class Frontend extends EventEmitter {
1953
1761
  return;
1954
1762
  }
1955
1763
  const config = plugin.configJson;
1956
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1957
1764
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1958
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1959
1765
  if (select === 'serial')
1960
1766
  this.log.info(`Selected device serial ${data.params.serial}`);
1961
1767
  if (select === 'name')
1962
1768
  this.log.info(`Selected device name ${data.params.name}`);
1963
1769
  if (config && select && (select === 'serial' || select === 'name')) {
1964
- // Remove postfix from the serial if it exists
1965
1770
  if (config.postfix) {
1966
1771
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1967
1772
  }
1968
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1969
1773
  if (isValidArray(config.whiteList, 1)) {
1970
1774
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1971
1775
  config.whiteList.push(data.params.serial);
@@ -1974,7 +1778,6 @@ export class Frontend extends EventEmitter {
1974
1778
  config.whiteList.push(data.params.name);
1975
1779
  }
1976
1780
  }
1977
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1978
1781
  if (isValidArray(config.blackList, 1)) {
1979
1782
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1980
1783
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2002,9 +1805,7 @@ export class Frontend extends EventEmitter {
2002
1805
  return;
2003
1806
  }
2004
1807
  const config = plugin.configJson;
2005
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2006
1808
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2007
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2008
1809
  if (select === 'serial')
2009
1810
  this.log.info(`Unselected device serial ${data.params.serial}`);
2010
1811
  if (select === 'name')
@@ -2013,7 +1814,6 @@ export class Frontend extends EventEmitter {
2013
1814
  if (config.postfix) {
2014
1815
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2015
1816
  }
2016
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2017
1817
  if (isValidArray(config.whiteList, 1)) {
2018
1818
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2019
1819
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2022,7 +1822,6 @@ export class Frontend extends EventEmitter {
2022
1822
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2023
1823
  }
2024
1824
  }
2025
- // Add the serial to the blackList
2026
1825
  if (isValidArray(config.blackList)) {
2027
1826
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2028
1827
  config.blackList.push(data.params.serial);
@@ -2045,7 +1844,6 @@ export class Frontend extends EventEmitter {
2045
1844
  }
2046
1845
  }
2047
1846
  else {
2048
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2049
1847
  const localData = data;
2050
1848
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2051
1849
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2055,46 +1853,23 @@ export class Frontend extends EventEmitter {
2055
1853
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2056
1854
  }
2057
1855
  }
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
- */
2071
1856
  wssSendLogMessage(level, time, name, message) {
2072
1857
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2073
1858
  return;
2074
1859
  if (!level || !time || !name || !message)
2075
1860
  return;
2076
- // Remove ANSI escape codes from the message
2077
- // eslint-disable-next-line no-control-regex
2078
1861
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2079
- // Remove leading asterisks from the message
2080
1862
  message = message.replace(/^\*+/, '');
2081
- // Replace all occurrences of \t and \n
2082
1863
  message = message.replace(/[\t\n]/g, '');
2083
- // Remove non-printable characters
2084
- // eslint-disable-next-line no-control-regex
2085
1864
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2086
- // Replace all occurrences of \" with "
2087
1865
  message = message.replace(/\\"/g, '"');
2088
- // Define the maximum allowed length for continuous characters without a space
2089
1866
  const maxContinuousLength = 100;
2090
1867
  const keepStartLength = 20;
2091
1868
  const keepEndLength = 20;
2092
- // Split the message into words
2093
1869
  if (level !== 'spawn') {
2094
1870
  message = message
2095
1871
  .split(' ')
2096
1872
  .map((word) => {
2097
- // If the word length exceeds the max continuous length, insert spaces and truncate
2098
1873
  if (word.length > maxContinuousLength) {
2099
1874
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2100
1875
  }
@@ -2102,34 +1877,14 @@ export class Frontend extends EventEmitter {
2102
1877
  })
2103
1878
  .join(' ');
2104
1879
  }
2105
- // Send the message to all connected clients
2106
1880
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2107
1881
  }
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
- */
2120
1882
  wssSendRefreshRequired(changed, params) {
2121
1883
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2122
1884
  return;
2123
1885
  this.log.debug('Sending a refresh required message to all connected clients');
2124
- // Send the message to all connected clients
2125
1886
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2126
1887
  }
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
- */
2133
1888
  wssSendRestartRequired(snackbar = true, fixed = false) {
2134
1889
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2135
1890
  return;
@@ -2138,14 +1893,8 @@ export class Frontend extends EventEmitter {
2138
1893
  this.matterbridge.fixedRestartRequired = fixed;
2139
1894
  if (snackbar === true)
2140
1895
  this.wssSendSnackbarMessage(`Restart required`, 0);
2141
- // Send the message to all connected clients
2142
1896
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2143
1897
  }
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
- */
2149
1898
  wssSendRestartNotRequired(snackbar = true) {
2150
1899
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2151
1900
  return;
@@ -2153,133 +1902,57 @@ export class Frontend extends EventEmitter {
2153
1902
  this.matterbridge.restartRequired = false;
2154
1903
  if (snackbar === true)
2155
1904
  this.wssSendCloseSnackbarMessage(`Restart required`);
2156
- // Send the message to all connected clients
2157
1905
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2158
1906
  }
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
- */
2164
1907
  wssSendUpdateRequired(devVersion = false) {
2165
1908
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2166
1909
  return;
2167
1910
  this.log.debug('Sending an update required message to all connected clients');
2168
1911
  this.matterbridge.updateRequired = true;
2169
- // Send the message to all connected clients
2170
1912
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2171
1913
  }
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
- */
2178
1914
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2179
1915
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2180
1916
  return;
2181
1917
  if (hasParameter('debug'))
2182
1918
  this.log.debug('Sending a cpu update message to all connected clients');
2183
- // Send the message to all connected clients
2184
1919
  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 } });
2185
1920
  }
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
- */
2197
1921
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2198
1922
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2199
1923
  return;
2200
1924
  if (hasParameter('debug'))
2201
1925
  this.log.debug('Sending a memory update message to all connected clients');
2202
- // Send the message to all connected clients
2203
1926
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2204
1927
  }
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
- */
2211
1928
  wssSendUptimeUpdate(systemUptime, processUptime) {
2212
1929
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2213
1930
  return;
2214
1931
  if (hasParameter('debug'))
2215
1932
  this.log.debug('Sending a uptime update message to all connected clients');
2216
- // Send the message to all connected clients
2217
1933
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2218
1934
  }
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
- */
2230
1935
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2231
1936
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2232
1937
  return;
2233
1938
  this.log.debug('Sending a snackbar message to all connected clients');
2234
- // Send the message to all connected clients
2235
1939
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2236
1940
  }
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
- */
2243
1941
  wssSendCloseSnackbarMessage(message) {
2244
1942
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2245
1943
  return;
2246
1944
  this.log.debug('Sending a close snackbar message to all connected clients');
2247
- // Send the message to all connected clients
2248
1945
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2249
1946
  }
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
- */
2266
1947
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2267
1948
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2268
1949
  return;
2269
1950
  this.log.debug('Sending an attribute update message to all connected clients');
2270
- // Send the message to all connected clients
2271
1951
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2272
1952
  }
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
- */
2279
1953
  wssBroadcastMessage(msg) {
2280
1954
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2281
1955
  return;
2282
- // Send the message to all connected clients
2283
1956
  const stringifiedMsg = JSON.stringify(msg);
2284
1957
  if (msg.method !== 'log')
2285
1958
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -2290,4 +1963,3 @@ export class Frontend extends EventEmitter {
2290
1963
  });
2291
1964
  }
2292
1965
  }
2293
- //# sourceMappingURL=frontend.js.map