matterbridge 3.3.1-dev-20251012-b3546f8 → 3.3.1

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 (291) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/broadcastServer.d.ts +105 -0
  3. package/dist/broadcastServer.d.ts.map +1 -0
  4. package/dist/broadcastServer.js +86 -1
  5. package/dist/broadcastServer.js.map +1 -0
  6. package/dist/broadcastServerTypes.d.ts +717 -0
  7. package/dist/broadcastServerTypes.d.ts.map +1 -0
  8. package/dist/broadcastServerTypes.js +24 -0
  9. package/dist/broadcastServerTypes.js.map +1 -0
  10. package/dist/cli.d.ts +26 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +143 -5
  13. package/dist/cli.js.map +1 -0
  14. package/dist/cliEmitter.d.ts +50 -0
  15. package/dist/cliEmitter.d.ts.map +1 -0
  16. package/dist/cliEmitter.js +37 -0
  17. package/dist/cliEmitter.js.map +1 -0
  18. package/dist/cliHistory.d.ts +70 -0
  19. package/dist/cliHistory.d.ts.map +1 -0
  20. package/dist/cliHistory.js +46 -2
  21. package/dist/cliHistory.js.map +1 -0
  22. package/dist/clusters/export.d.ts +2 -0
  23. package/dist/clusters/export.d.ts.map +1 -0
  24. package/dist/clusters/export.js +2 -0
  25. package/dist/clusters/export.js.map +1 -0
  26. package/dist/defaultConfigSchema.d.ts +28 -0
  27. package/dist/defaultConfigSchema.d.ts.map +1 -0
  28. package/dist/defaultConfigSchema.js +24 -0
  29. package/dist/defaultConfigSchema.js.map +1 -0
  30. package/dist/deviceManager.d.ts +117 -0
  31. package/dist/deviceManager.d.ts.map +1 -0
  32. package/dist/deviceManager.js +124 -1
  33. package/dist/deviceManager.js.map +1 -0
  34. package/dist/devices/airConditioner.d.ts +98 -0
  35. package/dist/devices/airConditioner.d.ts.map +1 -0
  36. package/dist/devices/airConditioner.js +57 -0
  37. package/dist/devices/airConditioner.js.map +1 -0
  38. package/dist/devices/batteryStorage.d.ts +48 -0
  39. package/dist/devices/batteryStorage.d.ts.map +1 -0
  40. package/dist/devices/batteryStorage.js +48 -1
  41. package/dist/devices/batteryStorage.js.map +1 -0
  42. package/dist/devices/cooktop.d.ts +60 -0
  43. package/dist/devices/cooktop.d.ts.map +1 -0
  44. package/dist/devices/cooktop.js +55 -0
  45. package/dist/devices/cooktop.js.map +1 -0
  46. package/dist/devices/dishwasher.d.ts +71 -0
  47. package/dist/devices/dishwasher.d.ts.map +1 -0
  48. package/dist/devices/dishwasher.js +57 -0
  49. package/dist/devices/dishwasher.js.map +1 -0
  50. package/dist/devices/evse.d.ts +75 -0
  51. package/dist/devices/evse.d.ts.map +1 -0
  52. package/dist/devices/evse.js +74 -10
  53. package/dist/devices/evse.js.map +1 -0
  54. package/dist/devices/export.d.ts +17 -0
  55. package/dist/devices/export.d.ts.map +1 -0
  56. package/dist/devices/export.js +5 -0
  57. package/dist/devices/export.js.map +1 -0
  58. package/dist/devices/extractorHood.d.ts +46 -0
  59. package/dist/devices/extractorHood.d.ts.map +1 -0
  60. package/dist/devices/extractorHood.js +42 -0
  61. package/dist/devices/extractorHood.js.map +1 -0
  62. package/dist/devices/heatPump.d.ts +47 -0
  63. package/dist/devices/heatPump.d.ts.map +1 -0
  64. package/dist/devices/heatPump.js +50 -2
  65. package/dist/devices/heatPump.js.map +1 -0
  66. package/dist/devices/laundryDryer.d.ts +67 -0
  67. package/dist/devices/laundryDryer.d.ts.map +1 -0
  68. package/dist/devices/laundryDryer.js +62 -3
  69. package/dist/devices/laundryDryer.js.map +1 -0
  70. package/dist/devices/laundryWasher.d.ts +81 -0
  71. package/dist/devices/laundryWasher.d.ts.map +1 -0
  72. package/dist/devices/laundryWasher.js +70 -4
  73. package/dist/devices/laundryWasher.js.map +1 -0
  74. package/dist/devices/microwaveOven.d.ts +168 -0
  75. package/dist/devices/microwaveOven.d.ts.map +1 -0
  76. package/dist/devices/microwaveOven.js +88 -5
  77. package/dist/devices/microwaveOven.js.map +1 -0
  78. package/dist/devices/oven.d.ts +105 -0
  79. package/dist/devices/oven.d.ts.map +1 -0
  80. package/dist/devices/oven.js +85 -0
  81. package/dist/devices/oven.js.map +1 -0
  82. package/dist/devices/refrigerator.d.ts +118 -0
  83. package/dist/devices/refrigerator.d.ts.map +1 -0
  84. package/dist/devices/refrigerator.js +102 -0
  85. package/dist/devices/refrigerator.js.map +1 -0
  86. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  87. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  88. package/dist/devices/roboticVacuumCleaner.js +100 -9
  89. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  90. package/dist/devices/solarPower.d.ts +40 -0
  91. package/dist/devices/solarPower.d.ts.map +1 -0
  92. package/dist/devices/solarPower.js +38 -0
  93. package/dist/devices/solarPower.js.map +1 -0
  94. package/dist/devices/speaker.d.ts +87 -0
  95. package/dist/devices/speaker.d.ts.map +1 -0
  96. package/dist/devices/speaker.js +84 -0
  97. package/dist/devices/speaker.js.map +1 -0
  98. package/dist/devices/temperatureControl.d.ts +166 -0
  99. package/dist/devices/temperatureControl.d.ts.map +1 -0
  100. package/dist/devices/temperatureControl.js +25 -3
  101. package/dist/devices/temperatureControl.js.map +1 -0
  102. package/dist/devices/waterHeater.d.ts +111 -0
  103. package/dist/devices/waterHeater.d.ts.map +1 -0
  104. package/dist/devices/waterHeater.js +82 -2
  105. package/dist/devices/waterHeater.js.map +1 -0
  106. package/dist/dgram/coap.d.ts +205 -0
  107. package/dist/dgram/coap.d.ts.map +1 -0
  108. package/dist/dgram/coap.js +126 -13
  109. package/dist/dgram/coap.js.map +1 -0
  110. package/dist/dgram/dgram.d.ts +141 -0
  111. package/dist/dgram/dgram.d.ts.map +1 -0
  112. package/dist/dgram/dgram.js +114 -2
  113. package/dist/dgram/dgram.js.map +1 -0
  114. package/dist/dgram/mb_coap.d.ts +24 -0
  115. package/dist/dgram/mb_coap.d.ts.map +1 -0
  116. package/dist/dgram/mb_coap.js +41 -3
  117. package/dist/dgram/mb_coap.js.map +1 -0
  118. package/dist/dgram/mb_mdns.d.ts +24 -0
  119. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  120. package/dist/dgram/mb_mdns.js +80 -15
  121. package/dist/dgram/mb_mdns.js.map +1 -0
  122. package/dist/dgram/mdns.d.ts +290 -0
  123. package/dist/dgram/mdns.d.ts.map +1 -0
  124. package/dist/dgram/mdns.js +299 -137
  125. package/dist/dgram/mdns.js.map +1 -0
  126. package/dist/dgram/multicast.d.ts +67 -0
  127. package/dist/dgram/multicast.d.ts.map +1 -0
  128. package/dist/dgram/multicast.js +62 -1
  129. package/dist/dgram/multicast.js.map +1 -0
  130. package/dist/dgram/unicast.d.ts +56 -0
  131. package/dist/dgram/unicast.d.ts.map +1 -0
  132. package/dist/dgram/unicast.js +54 -0
  133. package/dist/dgram/unicast.js.map +1 -0
  134. package/dist/frontend.d.ts +234 -0
  135. package/dist/frontend.d.ts.map +1 -0
  136. package/dist/frontend.js +402 -29
  137. package/dist/frontend.js.map +1 -0
  138. package/dist/frontendTypes.d.ts +522 -0
  139. package/dist/frontendTypes.d.ts.map +1 -0
  140. package/dist/frontendTypes.js +45 -0
  141. package/dist/frontendTypes.js.map +1 -0
  142. package/dist/helpers.d.ts +48 -0
  143. package/dist/helpers.d.ts.map +1 -0
  144. package/dist/helpers.js +53 -0
  145. package/dist/helpers.js.map +1 -0
  146. package/dist/index.d.ts +33 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +25 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/logger/export.d.ts +2 -0
  151. package/dist/logger/export.d.ts.map +1 -0
  152. package/dist/logger/export.js +1 -0
  153. package/dist/logger/export.js.map +1 -0
  154. package/dist/matter/behaviors.d.ts +2 -0
  155. package/dist/matter/behaviors.d.ts.map +1 -0
  156. package/dist/matter/behaviors.js +2 -0
  157. package/dist/matter/behaviors.js.map +1 -0
  158. package/dist/matter/clusters.d.ts +2 -0
  159. package/dist/matter/clusters.d.ts.map +1 -0
  160. package/dist/matter/clusters.js +2 -0
  161. package/dist/matter/clusters.js.map +1 -0
  162. package/dist/matter/devices.d.ts +2 -0
  163. package/dist/matter/devices.d.ts.map +1 -0
  164. package/dist/matter/devices.js +2 -0
  165. package/dist/matter/devices.js.map +1 -0
  166. package/dist/matter/endpoints.d.ts +2 -0
  167. package/dist/matter/endpoints.d.ts.map +1 -0
  168. package/dist/matter/endpoints.js +2 -0
  169. package/dist/matter/endpoints.js.map +1 -0
  170. package/dist/matter/export.d.ts +5 -0
  171. package/dist/matter/export.d.ts.map +1 -0
  172. package/dist/matter/export.js +3 -0
  173. package/dist/matter/export.js.map +1 -0
  174. package/dist/matter/types.d.ts +3 -0
  175. package/dist/matter/types.d.ts.map +1 -0
  176. package/dist/matter/types.js +3 -0
  177. package/dist/matter/types.js.map +1 -0
  178. package/dist/matterbridge.d.ts +469 -0
  179. package/dist/matterbridge.d.ts.map +1 -0
  180. package/dist/matterbridge.js +795 -45
  181. package/dist/matterbridge.js.map +1 -0
  182. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  183. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  184. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  185. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  186. package/dist/matterbridgeBehaviors.d.ts +1747 -0
  187. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  188. package/dist/matterbridgeBehaviors.js +65 -5
  189. package/dist/matterbridgeBehaviors.js.map +1 -0
  190. package/dist/matterbridgeDeviceTypes.d.ts +761 -0
  191. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  192. package/dist/matterbridgeDeviceTypes.js +630 -17
  193. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  194. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  195. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  196. package/dist/matterbridgeDynamicPlatform.js +36 -0
  197. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  198. package/dist/matterbridgeEndpoint.d.ts +1534 -0
  199. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  200. package/dist/matterbridgeEndpoint.js +1398 -58
  201. package/dist/matterbridgeEndpoint.js.map +1 -0
  202. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  203. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  204. package/dist/matterbridgeEndpointHelpers.js +345 -12
  205. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  206. package/dist/matterbridgePlatform.d.ts +402 -0
  207. package/dist/matterbridgePlatform.d.ts.map +1 -0
  208. package/dist/matterbridgePlatform.js +341 -1
  209. package/dist/matterbridgePlatform.js.map +1 -0
  210. package/dist/matterbridgeTypes.d.ts +209 -0
  211. package/dist/matterbridgeTypes.d.ts.map +1 -0
  212. package/dist/matterbridgeTypes.js +26 -0
  213. package/dist/matterbridgeTypes.js.map +1 -0
  214. package/dist/pluginManager.d.ts +353 -0
  215. package/dist/pluginManager.d.ts.map +1 -0
  216. package/dist/pluginManager.js +325 -3
  217. package/dist/pluginManager.js.map +1 -0
  218. package/dist/shelly.d.ts +174 -0
  219. package/dist/shelly.d.ts.map +1 -0
  220. package/dist/shelly.js +168 -7
  221. package/dist/shelly.js.map +1 -0
  222. package/dist/storage/export.d.ts +2 -0
  223. package/dist/storage/export.d.ts.map +1 -0
  224. package/dist/storage/export.js +1 -0
  225. package/dist/storage/export.js.map +1 -0
  226. package/dist/update.d.ts +75 -0
  227. package/dist/update.d.ts.map +1 -0
  228. package/dist/update.js +69 -0
  229. package/dist/update.js.map +1 -0
  230. package/dist/utils/colorUtils.d.ts +99 -0
  231. package/dist/utils/colorUtils.d.ts.map +1 -0
  232. package/dist/utils/colorUtils.js +97 -2
  233. package/dist/utils/colorUtils.js.map +1 -0
  234. package/dist/utils/commandLine.d.ts +59 -0
  235. package/dist/utils/commandLine.d.ts.map +1 -0
  236. package/dist/utils/commandLine.js +54 -0
  237. package/dist/utils/commandLine.js.map +1 -0
  238. package/dist/utils/copyDirectory.d.ts +33 -0
  239. package/dist/utils/copyDirectory.d.ts.map +1 -0
  240. package/dist/utils/copyDirectory.js +38 -1
  241. package/dist/utils/copyDirectory.js.map +1 -0
  242. package/dist/utils/createDirectory.d.ts +34 -0
  243. package/dist/utils/createDirectory.d.ts.map +1 -0
  244. package/dist/utils/createDirectory.js +33 -0
  245. package/dist/utils/createDirectory.js.map +1 -0
  246. package/dist/utils/createZip.d.ts +39 -0
  247. package/dist/utils/createZip.d.ts.map +1 -0
  248. package/dist/utils/createZip.js +47 -2
  249. package/dist/utils/createZip.js.map +1 -0
  250. package/dist/utils/deepCopy.d.ts +32 -0
  251. package/dist/utils/deepCopy.d.ts.map +1 -0
  252. package/dist/utils/deepCopy.js +39 -0
  253. package/dist/utils/deepCopy.js.map +1 -0
  254. package/dist/utils/deepEqual.d.ts +54 -0
  255. package/dist/utils/deepEqual.d.ts.map +1 -0
  256. package/dist/utils/deepEqual.js +72 -1
  257. package/dist/utils/deepEqual.js.map +1 -0
  258. package/dist/utils/error.d.ts +44 -0
  259. package/dist/utils/error.d.ts.map +1 -0
  260. package/dist/utils/error.js +41 -0
  261. package/dist/utils/error.js.map +1 -0
  262. package/dist/utils/export.d.ts +13 -0
  263. package/dist/utils/export.d.ts.map +1 -0
  264. package/dist/utils/export.js +1 -0
  265. package/dist/utils/export.js.map +1 -0
  266. package/dist/utils/hex.d.ts +89 -0
  267. package/dist/utils/hex.d.ts.map +1 -0
  268. package/dist/utils/hex.js +124 -0
  269. package/dist/utils/hex.js.map +1 -0
  270. package/dist/utils/isvalid.d.ts +103 -0
  271. package/dist/utils/isvalid.d.ts.map +1 -0
  272. package/dist/utils/isvalid.js +101 -0
  273. package/dist/utils/isvalid.js.map +1 -0
  274. package/dist/utils/jestHelpers.d.ts +137 -0
  275. package/dist/utils/jestHelpers.d.ts.map +1 -0
  276. package/dist/utils/jestHelpers.js +153 -3
  277. package/dist/utils/jestHelpers.js.map +1 -0
  278. package/dist/utils/network.d.ts +115 -0
  279. package/dist/utils/network.d.ts.map +1 -0
  280. package/dist/utils/network.js +108 -5
  281. package/dist/utils/network.js.map +1 -0
  282. package/dist/utils/spawn.d.ts +35 -0
  283. package/dist/utils/spawn.d.ts.map +1 -0
  284. package/dist/utils/spawn.js +71 -0
  285. package/dist/utils/spawn.js.map +1 -0
  286. package/dist/utils/wait.d.ts +54 -0
  287. package/dist/utils/wait.d.ts.map +1 -0
  288. package/dist/utils/wait.js +60 -8
  289. package/dist/utils/wait.js.map +1 -0
  290. package/npm-shrinkwrap.json +2 -2
  291. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,9 +1,35 @@
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @created 2025-01-13
7
+ * @version 1.3.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2025, 2026, 2027 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // eslint-disable-next-line no-console
1
25
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
26
  console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
3
27
  import os from 'node:os';
4
28
  import path from 'node:path';
5
29
  import EventEmitter from 'node:events';
30
+ // AnsiLogger module
6
31
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
32
+ // @matter
7
33
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
8
34
  import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
9
35
  import { PowerSource } from '@matter/main/clusters/power-source';
@@ -33,7 +59,7 @@ export class Frontend extends EventEmitter {
33
59
  constructor(matterbridge) {
34
60
  super();
35
61
  this.matterbridge = matterbridge;
36
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
62
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
37
63
  this.log.logNameColor = '\x1b[38;5;97m';
38
64
  this.server = new BroadcastServer('plugins', this.log);
39
65
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -95,13 +121,42 @@ export class Frontend extends EventEmitter {
95
121
  async start(port = 8283) {
96
122
  this.port = port;
97
123
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
124
+ // Initialize multer with the upload directory
98
125
  const multer = await import('multer');
99
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
126
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
100
127
  const upload = multer.default({ dest: uploadDir });
128
+ // Create the express app that serves the frontend
101
129
  const express = await import('express');
102
130
  this.expressApp = express.default();
131
+ // Inject logging/debug wrapper for route/middleware registration
132
+ /*
133
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
134
+ for (const method of methods) {
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
137
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
138
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
139
+ try {
140
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
141
+ return original(path, ...rest);
142
+ } catch (err) {
143
+ console.error(`[ERROR] Failed to register route: ${path}`);
144
+ throw err;
145
+ }
146
+ };
147
+ }
148
+ */
149
+ // Log all requests to the server for debugging
150
+ /*
151
+ this.expressApp.use((req, res, next) => {
152
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
153
+ next();
154
+ });
155
+ */
156
+ // Serve static files from 'frontend/build' directory
103
157
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
104
158
  if (!hasParameter('ssl')) {
159
+ // Create an HTTP server and attach the express app
105
160
  const http = await import('node:http');
106
161
  try {
107
162
  this.log.debug(`Creating HTTP server...`);
@@ -112,6 +167,7 @@ export class Frontend extends EventEmitter {
112
167
  this.emit('server_error', error);
113
168
  return;
114
169
  }
170
+ // Listen on the specified port
115
171
  if (hasParameter('ingress')) {
116
172
  this.httpServer.listen(this.port, '0.0.0.0', () => {
117
173
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -144,6 +200,7 @@ export class Frontend extends EventEmitter {
144
200
  });
145
201
  }
146
202
  else {
203
+ // SSL is enabled, load the certificate and the private key
147
204
  let cert;
148
205
  let key;
149
206
  let ca;
@@ -153,6 +210,7 @@ export class Frontend extends EventEmitter {
153
210
  let httpsServerOptions = {};
154
211
  const fs = await import('node:fs');
155
212
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
213
+ // Load the p12 certificate and the passphrase
156
214
  try {
157
215
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
158
216
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -164,7 +222,7 @@ export class Frontend extends EventEmitter {
164
222
  }
165
223
  try {
166
224
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
167
- passphrase = passphrase.trim();
225
+ passphrase = passphrase.trim(); // Ensure no extra characters
168
226
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
169
227
  }
170
228
  catch (error) {
@@ -175,6 +233,7 @@ export class Frontend extends EventEmitter {
175
233
  httpsServerOptions = { pfx, passphrase };
176
234
  }
177
235
  else {
236
+ // 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.
178
237
  try {
179
238
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
180
239
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -204,9 +263,10 @@ export class Frontend extends EventEmitter {
204
263
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
205
264
  }
206
265
  if (hasParameter('mtls')) {
207
- httpsServerOptions.requestCert = true;
208
- httpsServerOptions.rejectUnauthorized = true;
266
+ httpsServerOptions.requestCert = true; // Request client certificate
267
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
209
268
  }
269
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
210
270
  const https = await import('node:https');
211
271
  try {
212
272
  this.log.debug(`Creating HTTPS server...`);
@@ -217,6 +277,7 @@ export class Frontend extends EventEmitter {
217
277
  this.emit('server_error', error);
218
278
  return;
219
279
  }
280
+ // Listen on the specified port
220
281
  if (hasParameter('ingress')) {
221
282
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
222
283
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -248,16 +309,18 @@ export class Frontend extends EventEmitter {
248
309
  return;
249
310
  });
250
311
  }
312
+ // Create a WebSocket server and attach it to the http or https server
251
313
  const ws = await import('ws');
252
314
  this.log.debug(`Creating WebSocketServer...`);
253
315
  this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
254
316
  this.webSocketServer.on('connection', (ws, request) => {
255
317
  const clientIp = request.socket.remoteAddress;
256
- let callbackLogLevel = "notice";
257
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
258
- callbackLogLevel = "info";
259
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
260
- callbackLogLevel = "debug";
318
+ // Set the global logger callback for the WebSocketServer
319
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
320
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
321
+ callbackLogLevel = "info" /* LogLevel.INFO */;
322
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
323
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
261
324
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
262
325
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
263
326
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -279,6 +342,7 @@ export class Frontend extends EventEmitter {
279
342
  }
280
343
  });
281
344
  ws.on('error', (error) => {
345
+ // istanbul ignore next
282
346
  this.log.error(`WebSocket client error: ${error}`);
283
347
  });
284
348
  });
@@ -292,6 +356,7 @@ export class Frontend extends EventEmitter {
292
356
  this.webSocketServer.on('error', (ws, error) => {
293
357
  this.log.error(`WebSocketServer error: ${error}`);
294
358
  });
359
+ // Subscribe to cli events
295
360
  cliEmitter.removeAllListeners();
296
361
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
297
362
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -302,6 +367,8 @@ export class Frontend extends EventEmitter {
302
367
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
303
368
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
304
369
  });
370
+ // Endpoint to validate login code
371
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
305
372
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
306
373
  const { password } = req.body;
307
374
  this.log.debug('The frontend sent /api/login', password);
@@ -320,23 +387,27 @@ export class Frontend extends EventEmitter {
320
387
  this.log.warn('/api/login error wrong password');
321
388
  res.json({ valid: false });
322
389
  }
390
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
323
391
  }
324
392
  catch (error) {
325
393
  this.log.error('/api/login error getting password');
326
394
  res.json({ valid: false });
327
395
  }
328
396
  });
397
+ // Endpoint to provide health check for docker
329
398
  this.expressApp.get('/health', (req, res) => {
330
399
  this.log.debug('Express received /health');
331
400
  const healthStatus = {
332
- status: 'ok',
333
- uptime: process.uptime(),
334
- timestamp: new Date().toISOString(),
401
+ status: 'ok', // Indicate service is healthy
402
+ uptime: process.uptime(), // Server uptime in seconds
403
+ timestamp: new Date().toISOString(), // Current timestamp
335
404
  };
336
405
  res.status(200).json(healthStatus);
337
406
  });
407
+ // Endpoint to provide memory usage details
338
408
  this.expressApp.get('/memory', async (req, res) => {
339
409
  this.log.debug('Express received /memory');
410
+ // Memory usage from process
340
411
  const memoryUsageRaw = process.memoryUsage();
341
412
  const memoryUsage = {
342
413
  rss: formatMemoryUsage(memoryUsageRaw.rss),
@@ -345,10 +416,13 @@ export class Frontend extends EventEmitter {
345
416
  external: formatMemoryUsage(memoryUsageRaw.external),
346
417
  arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
347
418
  };
419
+ // V8 heap statistics
348
420
  const { default: v8 } = await import('node:v8');
349
421
  const heapStatsRaw = v8.getHeapStatistics();
350
422
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
423
+ // Format heapStats
351
424
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
425
+ // Format heapSpaces
352
426
  const heapSpaces = heapSpacesRaw.map((space) => ({
353
427
  ...space,
354
428
  space_size: formatMemoryUsage(space.space_size),
@@ -367,18 +441,22 @@ export class Frontend extends EventEmitter {
367
441
  };
368
442
  res.status(200).json(memoryReport);
369
443
  });
444
+ // Endpoint to provide settings
370
445
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
371
446
  this.log.debug('The frontend sent /api/settings');
372
447
  res.json(await this.getApiSettings());
373
448
  });
449
+ // Endpoint to provide plugins
374
450
  this.expressApp.get('/api/plugins', async (req, res) => {
375
451
  this.log.debug('The frontend sent /api/plugins');
376
452
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
377
453
  });
454
+ // Endpoint to provide devices
378
455
  this.expressApp.get('/api/devices', async (req, res) => {
379
456
  this.log.debug('The frontend sent /api/devices');
380
457
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
381
458
  });
459
+ // Endpoint to view the matterbridge log
382
460
  this.expressApp.get('/api/view-mblog', async (req, res) => {
383
461
  this.log.debug('The frontend sent /api/view-mblog');
384
462
  try {
@@ -392,6 +470,7 @@ export class Frontend extends EventEmitter {
392
470
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
393
471
  }
394
472
  });
473
+ // Endpoint to view the matter.js log
395
474
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
396
475
  this.log.debug('The frontend sent /api/view-mjlog');
397
476
  try {
@@ -405,9 +484,11 @@ export class Frontend extends EventEmitter {
405
484
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
406
485
  }
407
486
  });
487
+ // Endpoint to view the diagnostic.log
408
488
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
409
489
  this.log.debug('The frontend sent /api/view-diagnostic');
410
490
  const serverNodes = [];
491
+ // istanbul ignore else
411
492
  if (this.matterbridge.bridgeMode === 'bridge') {
412
493
  if (this.matterbridge.serverNode)
413
494
  serverNodes.push(this.matterbridge.serverNode);
@@ -418,6 +499,7 @@ export class Frontend extends EventEmitter {
418
499
  serverNodes.push(plugin.serverNode);
419
500
  }
420
501
  }
502
+ // istanbul ignore next
421
503
  for (const device of this.matterbridge.devices.array()) {
422
504
  if (device.serverNode)
423
505
  serverNodes.push(device.serverNode);
@@ -441,7 +523,7 @@ export class Frontend extends EventEmitter {
441
523
  values: [...serverNodes],
442
524
  })));
443
525
  delete Logger.destinations.diagnostic;
444
- await wait(500);
526
+ await wait(500); // Wait for the log to be written
445
527
  try {
446
528
  const fs = await import('node:fs');
447
529
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
@@ -449,10 +531,13 @@ export class Frontend extends EventEmitter {
449
531
  res.send(data.slice(29));
450
532
  }
451
533
  catch (error) {
534
+ // istanbul ignore next
452
535
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
536
+ // istanbul ignore next
453
537
  res.status(500).send('Error reading diagnostic log file.');
454
538
  }
455
539
  });
540
+ // Endpoint to view the history.html
456
541
  this.expressApp.get('/api/viewhistory', async (req, res) => {
457
542
  this.log.debug('The frontend sent /api/viewhistory');
458
543
  try {
@@ -466,6 +551,7 @@ export class Frontend extends EventEmitter {
466
551
  res.status(500).send('Error reading history log file. Please create the history log before loading it.');
467
552
  }
468
553
  });
554
+ // Endpoint to view the shelly log
469
555
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
470
556
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
471
557
  try {
@@ -479,6 +565,7 @@ export class Frontend extends EventEmitter {
479
565
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
480
566
  }
481
567
  });
568
+ // Endpoint to download the matterbridge log
482
569
  this.expressApp.get('/api/download-mblog', async (req, res) => {
483
570
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
484
571
  const fs = await import('node:fs');
@@ -493,12 +580,14 @@ export class Frontend extends EventEmitter {
493
580
  }
494
581
  res.type('text/plain');
495
582
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
583
+ /* istanbul ignore if */
496
584
  if (error) {
497
585
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
498
586
  res.status(500).send('Error downloading the matterbridge log file');
499
587
  }
500
588
  });
501
589
  });
590
+ // Endpoint to download the matter log
502
591
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
503
592
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
504
593
  const fs = await import('node:fs');
@@ -513,12 +602,14 @@ export class Frontend extends EventEmitter {
513
602
  }
514
603
  res.type('text/plain');
515
604
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
605
+ /* istanbul ignore if */
516
606
  if (error) {
517
607
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
518
608
  res.status(500).send('Error downloading the matter log file');
519
609
  }
520
610
  });
521
611
  });
612
+ // Endpoint to download the shelly log
522
613
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
523
614
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
524
615
  const fs = await import('node:fs');
@@ -533,75 +624,91 @@ export class Frontend extends EventEmitter {
533
624
  }
534
625
  res.type('text/plain');
535
626
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
627
+ /* istanbul ignore if */
536
628
  if (error) {
537
629
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
538
630
  res.status(500).send('Error downloading Shelly system log file');
539
631
  }
540
632
  });
541
633
  });
634
+ // Endpoint to download the matterbridge storage directory
542
635
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
543
636
  this.log.debug('The frontend sent /api/download-mbstorage');
544
637
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
545
638
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
639
+ /* istanbul ignore if */
546
640
  if (error) {
547
641
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
548
642
  res.status(500).send('Error downloading the matterbridge storage file');
549
643
  }
550
644
  });
551
645
  });
646
+ // Endpoint to download the matter storage file
552
647
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
553
648
  this.log.debug('The frontend sent /api/download-mjstorage');
554
649
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
555
650
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
651
+ /* istanbul ignore if */
556
652
  if (error) {
557
653
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
558
654
  res.status(500).send('Error downloading the matter storage zip file');
559
655
  }
560
656
  });
561
657
  });
658
+ // Endpoint to download the matterbridge plugin directory
562
659
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
563
660
  this.log.debug('The frontend sent /api/download-pluginstorage');
564
661
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
565
662
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
663
+ /* istanbul ignore if */
566
664
  if (error) {
567
665
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
568
666
  res.status(500).send('Error downloading the matterbridge plugin storage file');
569
667
  }
570
668
  });
571
669
  });
670
+ // Endpoint to download the matterbridge plugin config files
572
671
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
573
672
  this.log.debug('The frontend sent /api/download-pluginconfig');
574
673
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
575
674
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
675
+ /* istanbul ignore if */
576
676
  if (error) {
577
677
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
578
678
  res.status(500).send('Error downloading the matterbridge plugin config file');
579
679
  }
580
680
  });
581
681
  });
682
+ // Endpoint to download the matterbridge backup (created with the backup command)
582
683
  this.expressApp.get('/api/download-backup', async (req, res) => {
583
684
  this.log.debug('The frontend sent /api/download-backup');
584
685
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
686
+ /* istanbul ignore if */
585
687
  if (error) {
586
688
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
587
689
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
588
690
  }
589
691
  });
590
692
  });
693
+ // Endpoint to upload a package
591
694
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
592
695
  const { filename } = req.body;
593
696
  const file = req.file;
697
+ /* istanbul ignore if */
594
698
  if (!file || !filename) {
595
699
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
596
700
  res.status(400).send('Invalid request: file and filename are required');
597
701
  return;
598
702
  }
599
703
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
704
+ // Define the path where the plugin file will be saved
600
705
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
601
706
  try {
707
+ // Move the uploaded file to the specified path
602
708
  const fs = await import('node:fs');
603
709
  await fs.promises.rename(file.path, filePath);
604
710
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
711
+ // Install the plugin package
605
712
  if (filename.endsWith('.tgz')) {
606
713
  const { spawnCommand } = await import('./utils/spawn.js');
607
714
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -621,6 +728,7 @@ export class Frontend extends EventEmitter {
621
728
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
622
729
  }
623
730
  });
731
+ // Fallback for routing (must be the last route)
624
732
  this.expressApp.use((req, res) => {
625
733
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
626
734
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -630,13 +738,16 @@ export class Frontend extends EventEmitter {
630
738
  async stop() {
631
739
  this.log.debug('Stopping the frontend...');
632
740
  const ws = await import('ws');
741
+ // Remove listeners from the express app
633
742
  if (this.expressApp) {
634
743
  this.expressApp.removeAllListeners();
635
744
  this.expressApp = undefined;
636
745
  this.log.debug('Frontend app closed successfully');
637
746
  }
747
+ // Close the WebSocket server
638
748
  if (this.webSocketServer) {
639
749
  this.log.debug('Closing WebSocket server...');
750
+ // Close all active connections
640
751
  this.webSocketServer.clients.forEach((client) => {
641
752
  if (client.readyState === ws.WebSocket.OPEN) {
642
753
  client.close();
@@ -645,6 +756,7 @@ export class Frontend extends EventEmitter {
645
756
  await withTimeout(new Promise((resolve) => {
646
757
  this.webSocketServer?.close((error) => {
647
758
  if (error) {
759
+ // istanbul ignore next
648
760
  this.log.error(`Error closing WebSocket server: ${error}`);
649
761
  }
650
762
  else {
@@ -657,8 +769,27 @@ export class Frontend extends EventEmitter {
657
769
  this.webSocketServer.removeAllListeners();
658
770
  this.webSocketServer = undefined;
659
771
  }
772
+ // Close the http server
660
773
  if (this.httpServer) {
661
774
  this.log.debug('Closing http server...');
775
+ /*
776
+ await withTimeout(
777
+ new Promise<void>((resolve) => {
778
+ this.httpServer?.close((error) => {
779
+ if (error) {
780
+ // istanbul ignore next
781
+ this.log.error(`Error closing http server: ${error}`);
782
+ } else {
783
+ this.log.debug('Http server closed successfully');
784
+ this.emit('server_stopped');
785
+ }
786
+ resolve();
787
+ });
788
+ }),
789
+ 5000,
790
+ false,
791
+ );
792
+ */
662
793
  this.httpServer.close();
663
794
  this.log.debug('Http server closed successfully');
664
795
  this.listening = false;
@@ -667,8 +798,27 @@ export class Frontend extends EventEmitter {
667
798
  this.httpServer = undefined;
668
799
  this.log.debug('Frontend http server closed successfully');
669
800
  }
801
+ // Close the https server
670
802
  if (this.httpsServer) {
671
803
  this.log.debug('Closing https server...');
804
+ /*
805
+ await withTimeout(
806
+ new Promise<void>((resolve) => {
807
+ this.httpsServer?.close((error) => {
808
+ if (error) {
809
+ // istanbul ignore next
810
+ this.log.error(`Error closing https server: ${error}`);
811
+ } else {
812
+ this.log.debug('Https server closed successfully');
813
+ this.emit('server_stopped');
814
+ }
815
+ resolve();
816
+ });
817
+ }),
818
+ 5000,
819
+ false,
820
+ );
821
+ */
672
822
  this.httpsServer.close();
673
823
  this.log.debug('Https server closed successfully');
674
824
  this.listening = false;
@@ -679,7 +829,13 @@ export class Frontend extends EventEmitter {
679
829
  }
680
830
  this.log.debug('Frontend stopped successfully');
681
831
  }
832
+ /**
833
+ * Retrieves the api settings data.
834
+ *
835
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
836
+ */
682
837
  async getApiSettings() {
838
+ // Update the variable system information properties
683
839
  this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
684
840
  this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
685
841
  this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
@@ -689,6 +845,7 @@ export class Frontend extends EventEmitter {
689
845
  this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
690
846
  this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
691
847
  this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
848
+ // Create the matterbridge information
692
849
  const info = {
693
850
  homeDirectory: this.matterbridge.homeDirectory,
694
851
  rootDirectory: this.matterbridge.rootDirectory,
@@ -724,9 +881,15 @@ export class Frontend extends EventEmitter {
724
881
  };
725
882
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
726
883
  }
884
+ /**
885
+ * Retrieves the reachable attribute.
886
+ *
887
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
888
+ * @returns {boolean} The reachable attribute.
889
+ */
727
890
  getReachability(device) {
728
891
  if (this.matterbridge.hasCleanupStarted)
729
- return false;
892
+ return false; // Skip if cleanup has started
730
893
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
731
894
  return false;
732
895
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -737,9 +900,15 @@ export class Frontend extends EventEmitter {
737
900
  return true;
738
901
  return false;
739
902
  }
903
+ /**
904
+ * Retrieves the power source attribute.
905
+ *
906
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
907
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
908
+ */
740
909
  getPowerSource(endpoint) {
741
910
  if (this.matterbridge.hasCleanupStarted)
742
- return;
911
+ return; // Skip if cleanup has started
743
912
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
744
913
  return undefined;
745
914
  const powerSource = (device) => {
@@ -754,16 +923,25 @@ export class Frontend extends EventEmitter {
754
923
  }
755
924
  return;
756
925
  };
926
+ // Root endpoint
757
927
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
758
928
  return powerSource(endpoint);
929
+ // Child endpoints
759
930
  for (const child of endpoint.getChildEndpoints()) {
760
931
  if (child.hasClusterServer(PowerSource.Cluster.id))
761
932
  return powerSource(child);
762
933
  }
763
934
  }
935
+ /**
936
+ * Retrieves the cluster text description from a given device.
937
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
938
+ *
939
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
940
+ * @returns {string} The attributes description of the cluster servers in the device.
941
+ */
764
942
  getClusterTextFromDevice(device) {
765
943
  if (this.matterbridge.hasCleanupStarted)
766
- return '';
944
+ return ''; // Skip if cleanup has started
767
945
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
768
946
  return '';
769
947
  const getUserLabel = (device) => {
@@ -773,6 +951,7 @@ export class Frontend extends EventEmitter {
773
951
  if (composed)
774
952
  return 'Composed: ' + composed.value;
775
953
  }
954
+ // istanbul ignore next cause is not reachable
776
955
  return '';
777
956
  };
778
957
  const getFixedLabel = (device) => {
@@ -782,11 +961,13 @@ export class Frontend extends EventEmitter {
782
961
  if (composed)
783
962
  return 'Composed: ' + composed.value;
784
963
  }
964
+ // istanbul ignore next cause is not reacheable
785
965
  return '';
786
966
  };
787
967
  let attributes = '';
788
968
  let supportedModes = [];
789
969
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
970
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
790
971
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
791
972
  return;
792
973
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -876,11 +1057,17 @@ export class Frontend extends EventEmitter {
876
1057
  if (clusterName === 'userLabel' && attributeName === 'labelList')
877
1058
  attributes += `${getUserLabel(device)} `;
878
1059
  });
1060
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
879
1061
  return attributes.trimStart().trimEnd();
880
1062
  }
1063
+ /**
1064
+ * Retrieves the registered plugins sanitized for res.json().
1065
+ *
1066
+ * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1067
+ */
881
1068
  getPlugins() {
882
1069
  if (this.matterbridge.hasCleanupStarted)
883
- return [];
1070
+ return []; // Skip if cleanup has started
884
1071
  const plugins = [];
885
1072
  for (const plugin of this.matterbridge.plugins.array()) {
886
1073
  plugins.push({
@@ -908,18 +1095,27 @@ export class Frontend extends EventEmitter {
908
1095
  schemaJson: plugin.schemaJson,
909
1096
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
910
1097
  hasBlackList: plugin.configJson?.blackList !== undefined,
1098
+ // Childbridge mode specific data
911
1099
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
912
1100
  });
913
1101
  }
914
1102
  return plugins;
915
1103
  }
1104
+ /**
1105
+ * Retrieves the devices from Matterbridge.
1106
+ *
1107
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1108
+ * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1109
+ */
916
1110
  getDevices(pluginName) {
917
1111
  if (this.matterbridge.hasCleanupStarted)
918
- return [];
1112
+ return []; // Skip if cleanup has started
919
1113
  const devices = [];
920
1114
  for (const device of this.matterbridge.devices.array()) {
1115
+ // Filter by pluginName if provided
921
1116
  if (pluginName && pluginName !== device.plugin)
922
1117
  continue;
1118
+ // Check if the device has the required properties
923
1119
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
924
1120
  continue;
925
1121
  devices.push({
@@ -939,24 +1135,39 @@ export class Frontend extends EventEmitter {
939
1135
  }
940
1136
  return devices;
941
1137
  }
1138
+ /**
1139
+ * Retrieves the clusters from a given plugin and endpoint number.
1140
+ *
1141
+ * Response for /api/clusters
1142
+ *
1143
+ * @param {string} pluginName - The name of the plugin.
1144
+ * @param {number} endpointNumber - The endpoint number.
1145
+ * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1146
+ */
942
1147
  getClusters(pluginName, endpointNumber) {
943
1148
  if (this.matterbridge.hasCleanupStarted)
944
- return;
1149
+ return; // Skip if cleanup has started
945
1150
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
946
1151
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
947
1152
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
948
1153
  return;
949
1154
  }
1155
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1156
+ // Get the device types from the main endpoint
950
1157
  const deviceTypes = [];
951
1158
  const clusters = [];
952
1159
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
953
1160
  deviceTypes.push(d.deviceType);
954
1161
  });
1162
+ // Get the clusters from the main endpoint
955
1163
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
956
1164
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
957
1165
  return;
958
1166
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
959
1167
  return;
1168
+ // console.log(
1169
+ // `${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}`,
1170
+ // );
960
1171
  clusters.push({
961
1172
  endpoint: endpoint.number.toString(),
962
1173
  number: endpoint.number,
@@ -970,12 +1181,19 @@ export class Frontend extends EventEmitter {
970
1181
  attributeLocalValue: attributeValue,
971
1182
  });
972
1183
  });
1184
+ // Get the child endpoints
973
1185
  const childEndpoints = endpoint.getChildEndpoints();
1186
+ // if (childEndpoints.length === 0) {
1187
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1188
+ // }
974
1189
  childEndpoints.forEach((childEndpoint) => {
1190
+ // istanbul ignore if cause is not reachable: should never happen but ...
975
1191
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
976
1192
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
977
1193
  return;
978
1194
  }
1195
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1196
+ // Get the device types of the child endpoint
979
1197
  const deviceTypes = [];
980
1198
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
981
1199
  deviceTypes.push(d.deviceType);
@@ -985,6 +1203,9 @@ export class Frontend extends EventEmitter {
985
1203
  return;
986
1204
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
987
1205
  return;
1206
+ // console.log(
1207
+ // `${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}`,
1208
+ // );
988
1209
  clusters.push({
989
1210
  endpoint: childEndpoint.number.toString(),
990
1211
  number: childEndpoint.number,
@@ -1001,6 +1222,13 @@ export class Frontend extends EventEmitter {
1001
1222
  });
1002
1223
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
1003
1224
  }
1225
+ /**
1226
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1227
+ *
1228
+ * @param {WebSocket} client - The websocket client that sent the message.
1229
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1230
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1231
+ */
1004
1232
  async wsMessageHandler(client, message) {
1005
1233
  let data;
1006
1234
  const sendResponse = (data) => {
@@ -1020,7 +1248,7 @@ export class Frontend extends EventEmitter {
1020
1248
  };
1021
1249
  try {
1022
1250
  data = JSON.parse(message.toString());
1023
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1251
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1024
1252
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1025
1253
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1026
1254
  return;
@@ -1094,6 +1322,7 @@ export class Frontend extends EventEmitter {
1094
1322
  return;
1095
1323
  })
1096
1324
  .catch((_error) => {
1325
+ //
1097
1326
  });
1098
1327
  }
1099
1328
  else {
@@ -1141,6 +1370,7 @@ export class Frontend extends EventEmitter {
1141
1370
  return;
1142
1371
  })
1143
1372
  .catch((_error) => {
1373
+ //
1144
1374
  });
1145
1375
  }
1146
1376
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1166,6 +1396,7 @@ export class Frontend extends EventEmitter {
1166
1396
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1167
1397
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1168
1398
  if (plugin.serverNode) {
1399
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1169
1400
  await this.matterbridge.stopServerNode(plugin.serverNode);
1170
1401
  plugin.serverNode = undefined;
1171
1402
  }
@@ -1175,18 +1406,20 @@ export class Frontend extends EventEmitter {
1175
1406
  this.matterbridge.devices.remove(device);
1176
1407
  }
1177
1408
  }
1409
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1178
1410
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1179
1411
  await this.matterbridge.createDynamicPlugin(plugin);
1180
1412
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1181
- plugin.restartRequired = false;
1413
+ plugin.restartRequired = false; // Reset plugin restartRequired
1182
1414
  let needRestart = 0;
1183
1415
  for (const plugin of this.matterbridge.plugins) {
1184
1416
  if (plugin.restartRequired)
1185
1417
  needRestart++;
1186
1418
  }
1187
1419
  if (needRestart === 0) {
1188
- this.wssSendRestartNotRequired(true);
1420
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1189
1421
  }
1422
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1190
1423
  if (plugin.serverNode)
1191
1424
  await this.matterbridge.startServerNode(plugin.serverNode);
1192
1425
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1447,22 +1680,22 @@ export class Frontend extends EventEmitter {
1447
1680
  if (isValidString(data.params.value, 4)) {
1448
1681
  this.log.debug('Matterbridge logger level:', data.params.value);
1449
1682
  if (data.params.value === 'Debug') {
1450
- await this.matterbridge.setLogLevel("debug");
1683
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1451
1684
  }
1452
1685
  else if (data.params.value === 'Info') {
1453
- await this.matterbridge.setLogLevel("info");
1686
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1454
1687
  }
1455
1688
  else if (data.params.value === 'Notice') {
1456
- await this.matterbridge.setLogLevel("notice");
1689
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1457
1690
  }
1458
1691
  else if (data.params.value === 'Warn') {
1459
- await this.matterbridge.setLogLevel("warn");
1692
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1460
1693
  }
1461
1694
  else if (data.params.value === 'Error') {
1462
- await this.matterbridge.setLogLevel("error");
1695
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1463
1696
  }
1464
1697
  else if (data.params.value === 'Fatal') {
1465
- await this.matterbridge.setLogLevel("fatal");
1698
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1466
1699
  }
1467
1700
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1468
1701
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1473,6 +1706,7 @@ export class Frontend extends EventEmitter {
1473
1706
  this.log.debug('Matterbridge file log:', data.params.value);
1474
1707
  this.matterbridge.fileLogger = data.params.value;
1475
1708
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1709
+ // Create the file logger for matterbridge
1476
1710
  if (data.params.value)
1477
1711
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1478
1712
  else
@@ -1550,6 +1784,7 @@ export class Frontend extends EventEmitter {
1550
1784
  }
1551
1785
  break;
1552
1786
  case 'setmatterport':
1787
+ // eslint-disable-next-line no-case-declarations
1553
1788
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1554
1789
  if (isValidNumber(port, 5540, 5600)) {
1555
1790
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1569,6 +1804,7 @@ export class Frontend extends EventEmitter {
1569
1804
  }
1570
1805
  break;
1571
1806
  case 'setmatterdiscriminator':
1807
+ // eslint-disable-next-line no-case-declarations
1572
1808
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1573
1809
  if (isValidNumber(discriminator, 0, 4095)) {
1574
1810
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1588,6 +1824,7 @@ export class Frontend extends EventEmitter {
1588
1824
  }
1589
1825
  break;
1590
1826
  case 'setmatterpasscode':
1827
+ // eslint-disable-next-line no-case-declarations
1591
1828
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1592
1829
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1593
1830
  this.matterbridge.passcode = passcode;
@@ -1633,15 +1870,19 @@ export class Frontend extends EventEmitter {
1633
1870
  return;
1634
1871
  }
1635
1872
  const config = plugin.configJson;
1873
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1636
1874
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1875
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1637
1876
  if (select === 'serial')
1638
1877
  this.log.info(`Selected device serial ${data.params.serial}`);
1639
1878
  if (select === 'name')
1640
1879
  this.log.info(`Selected device name ${data.params.name}`);
1641
1880
  if (config && select && (select === 'serial' || select === 'name')) {
1881
+ // Remove postfix from the serial if it exists
1642
1882
  if (config.postfix) {
1643
1883
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1644
1884
  }
1885
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1645
1886
  if (isValidArray(config.whiteList, 1)) {
1646
1887
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1647
1888
  config.whiteList.push(data.params.serial);
@@ -1650,6 +1891,7 @@ export class Frontend extends EventEmitter {
1650
1891
  config.whiteList.push(data.params.name);
1651
1892
  }
1652
1893
  }
1894
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1653
1895
  if (isValidArray(config.blackList, 1)) {
1654
1896
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1655
1897
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1677,7 +1919,9 @@ export class Frontend extends EventEmitter {
1677
1919
  return;
1678
1920
  }
1679
1921
  const config = plugin.configJson;
1922
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1680
1923
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1924
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1681
1925
  if (select === 'serial')
1682
1926
  this.log.info(`Unselected device serial ${data.params.serial}`);
1683
1927
  if (select === 'name')
@@ -1686,6 +1930,7 @@ export class Frontend extends EventEmitter {
1686
1930
  if (config.postfix) {
1687
1931
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1688
1932
  }
1933
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1689
1934
  if (isValidArray(config.whiteList, 1)) {
1690
1935
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1691
1936
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1694,6 +1939,7 @@ export class Frontend extends EventEmitter {
1694
1939
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1695
1940
  }
1696
1941
  }
1942
+ // Add the serial to the blackList
1697
1943
  if (isValidArray(config.blackList)) {
1698
1944
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1699
1945
  config.blackList.push(data.params.serial);
@@ -1716,6 +1962,7 @@ export class Frontend extends EventEmitter {
1716
1962
  }
1717
1963
  }
1718
1964
  else {
1965
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1719
1966
  const localData = data;
1720
1967
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1721
1968
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1725,23 +1972,46 @@ export class Frontend extends EventEmitter {
1725
1972
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1726
1973
  }
1727
1974
  }
1975
+ /**
1976
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1977
+ *
1978
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1979
+ * @param {string} time - The time string of the message
1980
+ * @param {string} name - The logger name of the message
1981
+ * @param {string} message - The content of the message.
1982
+ *
1983
+ * @remarks
1984
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1985
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1986
+ * The function sends the message to all connected clients.
1987
+ */
1728
1988
  wssSendLogMessage(level, time, name, message) {
1729
1989
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1730
1990
  return;
1731
1991
  if (!level || !time || !name || !message)
1732
1992
  return;
1993
+ // Remove ANSI escape codes from the message
1994
+ // eslint-disable-next-line no-control-regex
1733
1995
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1996
+ // Remove leading asterisks from the message
1734
1997
  message = message.replace(/^\*+/, '');
1998
+ // Replace all occurrences of \t and \n
1735
1999
  message = message.replace(/[\t\n]/g, '');
2000
+ // Remove non-printable characters
2001
+ // eslint-disable-next-line no-control-regex
1736
2002
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2003
+ // Replace all occurrences of \" with "
1737
2004
  message = message.replace(/\\"/g, '"');
2005
+ // Define the maximum allowed length for continuous characters without a space
1738
2006
  const maxContinuousLength = 100;
1739
2007
  const keepStartLength = 20;
1740
2008
  const keepEndLength = 20;
2009
+ // Split the message into words
1741
2010
  if (level !== 'spawn') {
1742
2011
  message = message
1743
2012
  .split(' ')
1744
2013
  .map((word) => {
2014
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1745
2015
  if (word.length > maxContinuousLength) {
1746
2016
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1747
2017
  }
@@ -1749,14 +2019,34 @@ export class Frontend extends EventEmitter {
1749
2019
  })
1750
2020
  .join(' ');
1751
2021
  }
2022
+ // Send the message to all connected clients
1752
2023
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
1753
2024
  }
2025
+ /**
2026
+ * Sends a need to refresh WebSocket message to all connected clients.
2027
+ *
2028
+ * @param {string} changed - The changed value.
2029
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2030
+ * possible values for changed:
2031
+ * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2032
+ * - 'plugins'
2033
+ * - 'devices'
2034
+ * - 'matter' with param 'matter' (QRDiv component)
2035
+ * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2036
+ */
1754
2037
  wssSendRefreshRequired(changed, params) {
1755
2038
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1756
2039
  return;
1757
2040
  this.log.debug('Sending a refresh required message to all connected clients');
2041
+ // Send the message to all connected clients
1758
2042
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
1759
2043
  }
2044
+ /**
2045
+ * Sends a need to restart WebSocket message to all connected clients.
2046
+ *
2047
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2048
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2049
+ */
1760
2050
  wssSendRestartRequired(snackbar = true, fixed = false) {
1761
2051
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1762
2052
  return;
@@ -1765,8 +2055,14 @@ export class Frontend extends EventEmitter {
1765
2055
  this.matterbridge.fixedRestartRequired = fixed;
1766
2056
  if (snackbar === true)
1767
2057
  this.wssSendSnackbarMessage(`Restart required`, 0);
2058
+ // Send the message to all connected clients
1768
2059
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1769
2060
  }
2061
+ /**
2062
+ * Sends a no need to restart WebSocket message to all connected clients.
2063
+ *
2064
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2065
+ */
1770
2066
  wssSendRestartNotRequired(snackbar = true) {
1771
2067
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1772
2068
  return;
@@ -1774,57 +2070,133 @@ export class Frontend extends EventEmitter {
1774
2070
  this.matterbridge.restartRequired = false;
1775
2071
  if (snackbar === true)
1776
2072
  this.wssSendCloseSnackbarMessage(`Restart required`);
2073
+ // Send the message to all connected clients
1777
2074
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1778
2075
  }
2076
+ /**
2077
+ * Sends a need to update WebSocket message to all connected clients.
2078
+ *
2079
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2080
+ */
1779
2081
  wssSendUpdateRequired(devVersion = false) {
1780
2082
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1781
2083
  return;
1782
2084
  this.log.debug('Sending an update required message to all connected clients');
1783
2085
  this.matterbridge.updateRequired = true;
2086
+ // Send the message to all connected clients
1784
2087
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1785
2088
  }
2089
+ /**
2090
+ * Sends a cpu update message to all connected clients.
2091
+ *
2092
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2093
+ * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2094
+ */
1786
2095
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
1787
2096
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1788
2097
  return;
1789
2098
  if (hasParameter('debug'))
1790
2099
  this.log.debug('Sending a cpu update message to all connected clients');
2100
+ // Send the message to all connected clients
1791
2101
  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 } });
1792
2102
  }
2103
+ /**
2104
+ * Sends a memory update message to all connected clients.
2105
+ *
2106
+ * @param {string} totalMemory - The total memory in bytes.
2107
+ * @param {string} freeMemory - The free memory in bytes.
2108
+ * @param {string} rss - The resident set size in bytes.
2109
+ * @param {string} heapTotal - The total heap memory in bytes.
2110
+ * @param {string} heapUsed - The used heap memory in bytes.
2111
+ * @param {string} external - The external memory in bytes.
2112
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2113
+ */
1793
2114
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1794
2115
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1795
2116
  return;
1796
2117
  if (hasParameter('debug'))
1797
2118
  this.log.debug('Sending a memory update message to all connected clients');
2119
+ // Send the message to all connected clients
1798
2120
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1799
2121
  }
2122
+ /**
2123
+ * Sends an uptime update message to all connected clients.
2124
+ *
2125
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2126
+ * @param {string} processUptime - The process uptime in a human-readable format.
2127
+ */
1800
2128
  wssSendUptimeUpdate(systemUptime, processUptime) {
1801
2129
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1802
2130
  return;
1803
2131
  if (hasParameter('debug'))
1804
2132
  this.log.debug('Sending a uptime update message to all connected clients');
2133
+ // Send the message to all connected clients
1805
2134
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
1806
2135
  }
2136
+ /**
2137
+ * Sends an open snackbar message to all connected clients.
2138
+ *
2139
+ * @param {string} message - The message to send.
2140
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2141
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2142
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2143
+ *
2144
+ * @remarks
2145
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2146
+ */
1807
2147
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1808
2148
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1809
2149
  return;
1810
2150
  this.log.debug('Sending a snackbar message to all connected clients');
2151
+ // Send the message to all connected clients
1811
2152
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
1812
2153
  }
2154
+ /**
2155
+ * Sends a close snackbar message to all connected clients.
2156
+ * It will close the snackbar message with the same message and timeout = 0.
2157
+ *
2158
+ * @param {string} message - The message to send.
2159
+ */
1813
2160
  wssSendCloseSnackbarMessage(message) {
1814
2161
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1815
2162
  return;
1816
2163
  this.log.debug('Sending a close snackbar message to all connected clients');
2164
+ // Send the message to all connected clients
1817
2165
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1818
2166
  }
2167
+ /**
2168
+ * Sends an attribute update message to all connected WebSocket clients.
2169
+ *
2170
+ * @param {string | undefined} plugin - The name of the plugin.
2171
+ * @param {string | undefined} serialNumber - The serial number of the device.
2172
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2173
+ * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2174
+ * @param {string} id - The endpoint id where the attribute belongs.
2175
+ * @param {string} cluster - The cluster name where the attribute belongs.
2176
+ * @param {string} attribute - The name of the attribute that changed.
2177
+ * @param {number | string | boolean} value - The new value of the attribute.
2178
+ *
2179
+ * @remarks
2180
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2181
+ * with the updated attribute information.
2182
+ */
1819
2183
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1820
2184
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1821
2185
  return;
1822
2186
  this.log.debug('Sending an attribute update message to all connected clients');
2187
+ // Send the message to all connected clients
1823
2188
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1824
2189
  }
2190
+ /**
2191
+ * Sends a message to all connected clients.
2192
+ * This is an helper function to send a broadcast message to all connected clients.
2193
+ *
2194
+ * @param {WsMessageBroadcast} msg - The message to send.
2195
+ */
1825
2196
  wssBroadcastMessage(msg) {
1826
2197
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1827
2198
  return;
2199
+ // Send the message to all connected clients
1828
2200
  const stringifiedMsg = JSON.stringify(msg);
1829
2201
  if (msg.method !== 'log')
1830
2202
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -1835,3 +2207,4 @@ export class Frontend extends EventEmitter {
1835
2207
  });
1836
2208
  }
1837
2209
  }
2210
+ //# sourceMappingURL=frontend.js.map