matterbridge 3.3.3-dev-20251017-2e6040a → 3.3.3

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 +1 -1
  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 +719 -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 +135 -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 +74 -0
  19. package/dist/cliHistory.d.ts.map +1 -0
  20. package/dist/cliHistory.js +44 -0
  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 +235 -0
  135. package/dist/frontend.d.ts.map +1 -0
  136. package/dist/frontend.js +415 -29
  137. package/dist/frontend.js.map +1 -0
  138. package/dist/frontendTypes.d.ts +529 -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 +2399 -0
  187. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  188. package/dist/matterbridgeBehaviors.js +71 -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 +1545 -0
  199. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  200. package/dist/matterbridgeEndpoint.js +1412 -58
  201. package/dist/matterbridgeEndpoint.js.map +1 -0
  202. package/dist/matterbridgeEndpointHelpers.d.ts +560 -0
  203. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  204. package/dist/matterbridgeEndpointHelpers.js +368 -10
  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('frontend', this.log);
39
65
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -93,13 +119,42 @@ export class Frontend extends EventEmitter {
93
119
  async start(port = 8283) {
94
120
  this.port = port;
95
121
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
122
+ // Initialize multer with the upload directory
96
123
  const multer = await import('multer');
97
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
124
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
98
125
  const upload = multer.default({ dest: uploadDir });
126
+ // Create the express app that serves the frontend
99
127
  const express = await import('express');
100
128
  this.expressApp = express.default();
129
+ // Inject logging/debug wrapper for route/middleware registration
130
+ /*
131
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
132
+ for (const method of methods) {
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
137
+ try {
138
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
139
+ return original(path, ...rest);
140
+ } catch (err) {
141
+ console.error(`[ERROR] Failed to register route: ${path}`);
142
+ throw err;
143
+ }
144
+ };
145
+ }
146
+ */
147
+ // Log all requests to the server for debugging
148
+ /*
149
+ this.expressApp.use((req, res, next) => {
150
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
151
+ next();
152
+ });
153
+ */
154
+ // Serve static files from 'frontend/build' directory
101
155
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
102
156
  if (!hasParameter('ssl')) {
157
+ // Create an HTTP server and attach the express app
103
158
  const http = await import('node:http');
104
159
  try {
105
160
  this.log.debug(`Creating HTTP server...`);
@@ -110,6 +165,7 @@ export class Frontend extends EventEmitter {
110
165
  this.emit('server_error', error);
111
166
  return;
112
167
  }
168
+ // Listen on the specified port
113
169
  if (hasParameter('ingress')) {
114
170
  this.httpServer.listen(this.port, '0.0.0.0', () => {
115
171
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -142,6 +198,7 @@ export class Frontend extends EventEmitter {
142
198
  });
143
199
  }
144
200
  else {
201
+ // SSL is enabled, load the certificate and the private key
145
202
  let cert;
146
203
  let key;
147
204
  let ca;
@@ -151,6 +208,7 @@ export class Frontend extends EventEmitter {
151
208
  let httpsServerOptions = {};
152
209
  const fs = await import('node:fs');
153
210
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
211
+ // Load the p12 certificate and the passphrase
154
212
  try {
155
213
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
156
214
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -162,7 +220,7 @@ export class Frontend extends EventEmitter {
162
220
  }
163
221
  try {
164
222
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
165
- passphrase = passphrase.trim();
223
+ passphrase = passphrase.trim(); // Ensure no extra characters
166
224
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
167
225
  }
168
226
  catch (error) {
@@ -173,6 +231,7 @@ export class Frontend extends EventEmitter {
173
231
  httpsServerOptions = { pfx, passphrase };
174
232
  }
175
233
  else {
234
+ // 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.
176
235
  try {
177
236
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
178
237
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -202,9 +261,10 @@ export class Frontend extends EventEmitter {
202
261
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
203
262
  }
204
263
  if (hasParameter('mtls')) {
205
- httpsServerOptions.requestCert = true;
206
- httpsServerOptions.rejectUnauthorized = true;
264
+ httpsServerOptions.requestCert = true; // Request client certificate
265
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
207
266
  }
267
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
208
268
  const https = await import('node:https');
209
269
  try {
210
270
  this.log.debug(`Creating HTTPS server...`);
@@ -215,6 +275,7 @@ export class Frontend extends EventEmitter {
215
275
  this.emit('server_error', error);
216
276
  return;
217
277
  }
278
+ // Listen on the specified port
218
279
  if (hasParameter('ingress')) {
219
280
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
220
281
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -246,16 +307,18 @@ export class Frontend extends EventEmitter {
246
307
  return;
247
308
  });
248
309
  }
310
+ // Create a WebSocket server and attach it to the http or https server
249
311
  const ws = await import('ws');
250
312
  this.log.debug(`Creating WebSocketServer...`);
251
313
  this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
252
314
  this.webSocketServer.on('connection', (ws, request) => {
253
315
  const clientIp = request.socket.remoteAddress;
254
- let callbackLogLevel = "notice";
255
- if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
256
- callbackLogLevel = "info";
257
- if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
258
- callbackLogLevel = "debug";
316
+ // Set the global logger callback for the WebSocketServer
317
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
318
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
319
+ callbackLogLevel = "info" /* LogLevel.INFO */;
320
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
321
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
259
322
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
260
323
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
261
324
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -277,6 +340,7 @@ export class Frontend extends EventEmitter {
277
340
  }
278
341
  });
279
342
  ws.on('error', (error) => {
343
+ // istanbul ignore next
280
344
  this.log.error(`WebSocket client error: ${error}`);
281
345
  });
282
346
  });
@@ -290,6 +354,7 @@ export class Frontend extends EventEmitter {
290
354
  this.webSocketServer.on('error', (ws, error) => {
291
355
  this.log.error(`WebSocketServer error: ${error}`);
292
356
  });
357
+ // Subscribe to cli events
293
358
  cliEmitter.removeAllListeners();
294
359
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
295
360
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -300,6 +365,8 @@ export class Frontend extends EventEmitter {
300
365
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
301
366
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
302
367
  });
368
+ // Endpoint to validate login code
369
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
303
370
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
304
371
  const { password } = req.body;
305
372
  this.log.debug('The frontend sent /api/login', password);
@@ -318,23 +385,27 @@ export class Frontend extends EventEmitter {
318
385
  this.log.warn('/api/login error wrong password');
319
386
  res.json({ valid: false });
320
387
  }
388
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
321
389
  }
322
390
  catch (error) {
323
391
  this.log.error('/api/login error getting password');
324
392
  res.json({ valid: false });
325
393
  }
326
394
  });
395
+ // Endpoint to provide health check for docker
327
396
  this.expressApp.get('/health', (req, res) => {
328
397
  this.log.debug('Express received /health');
329
398
  const healthStatus = {
330
- status: 'ok',
331
- uptime: process.uptime(),
332
- timestamp: new Date().toISOString(),
399
+ status: 'ok', // Indicate service is healthy
400
+ uptime: process.uptime(), // Server uptime in seconds
401
+ timestamp: new Date().toISOString(), // Current timestamp
333
402
  };
334
403
  res.status(200).json(healthStatus);
335
404
  });
405
+ // Endpoint to provide memory usage details
336
406
  this.expressApp.get('/memory', async (req, res) => {
337
407
  this.log.debug('Express received /memory');
408
+ // Memory usage from process
338
409
  const memoryUsageRaw = process.memoryUsage();
339
410
  const memoryUsage = {
340
411
  rss: formatMemoryUsage(memoryUsageRaw.rss),
@@ -343,10 +414,13 @@ export class Frontend extends EventEmitter {
343
414
  external: formatMemoryUsage(memoryUsageRaw.external),
344
415
  arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
345
416
  };
417
+ // V8 heap statistics
346
418
  const { default: v8 } = await import('node:v8');
347
419
  const heapStatsRaw = v8.getHeapStatistics();
348
420
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
421
+ // Format heapStats
349
422
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
423
+ // Format heapSpaces
350
424
  const heapSpaces = heapSpacesRaw.map((space) => ({
351
425
  ...space,
352
426
  space_size: formatMemoryUsage(space.space_size),
@@ -365,18 +439,22 @@ export class Frontend extends EventEmitter {
365
439
  };
366
440
  res.status(200).json(memoryReport);
367
441
  });
442
+ // Endpoint to provide settings
368
443
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
369
444
  this.log.debug('The frontend sent /api/settings');
370
445
  res.json(await this.getApiSettings());
371
446
  });
447
+ // Endpoint to provide plugins
372
448
  this.expressApp.get('/api/plugins', async (req, res) => {
373
449
  this.log.debug('The frontend sent /api/plugins');
374
450
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
375
451
  });
452
+ // Endpoint to provide devices
376
453
  this.expressApp.get('/api/devices', async (req, res) => {
377
454
  this.log.debug('The frontend sent /api/devices');
378
455
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
379
456
  });
457
+ // Endpoint to view the matterbridge log
380
458
  this.expressApp.get('/api/view-mblog', async (req, res) => {
381
459
  this.log.debug('The frontend sent /api/view-mblog');
382
460
  try {
@@ -390,6 +468,7 @@ export class Frontend extends EventEmitter {
390
468
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
391
469
  }
392
470
  });
471
+ // Endpoint to view the matter.js log
393
472
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
394
473
  this.log.debug('The frontend sent /api/view-mjlog');
395
474
  try {
@@ -403,6 +482,7 @@ export class Frontend extends EventEmitter {
403
482
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
404
483
  }
405
484
  });
485
+ // Endpoint to view the diagnostic.log
406
486
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
407
487
  this.log.debug('The frontend sent /api/view-diagnostic');
408
488
  await this.generateDiagnostic();
@@ -413,10 +493,13 @@ export class Frontend extends EventEmitter {
413
493
  res.send(data.slice(29));
414
494
  }
415
495
  catch (error) {
496
+ // istanbul ignore next
416
497
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
498
+ // istanbul ignore next
417
499
  res.status(500).send('Error reading diagnostic log file.');
418
500
  }
419
501
  });
502
+ // Endpoint to download the diagnostic.log
420
503
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
421
504
  this.log.debug(`The frontend sent /api/download-diagnostic`);
422
505
  await this.generateDiagnostic();
@@ -427,16 +510,19 @@ export class Frontend extends EventEmitter {
427
510
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
428
511
  }
429
512
  catch (error) {
513
+ // istanbul ignore next
430
514
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
431
515
  }
432
516
  res.type('text/plain');
433
517
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
518
+ /* istanbul ignore if */
434
519
  if (error) {
435
520
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
436
521
  res.status(500).send('Error downloading the diagnostic log file');
437
522
  }
438
523
  });
439
524
  });
525
+ // Endpoint to view the history.html
440
526
  this.expressApp.get('/api/viewhistory', async (req, res) => {
441
527
  this.log.debug('The frontend sent /api/viewhistory');
442
528
  try {
@@ -450,6 +536,7 @@ export class Frontend extends EventEmitter {
450
536
  res.status(500).send('Error reading history file.');
451
537
  }
452
538
  });
539
+ // Endpoint to download the history.html
453
540
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
454
541
  this.log.debug(`The frontend sent /api/downloadhistory`);
455
542
  try {
@@ -459,6 +546,7 @@ export class Frontend extends EventEmitter {
459
546
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
460
547
  res.type('text/plain');
461
548
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
549
+ /* istanbul ignore if */
462
550
  if (error) {
463
551
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
464
552
  res.status(500).send('Error downloading history file');
@@ -470,6 +558,7 @@ export class Frontend extends EventEmitter {
470
558
  res.status(500).send('Error reading history file.');
471
559
  }
472
560
  });
561
+ // Endpoint to view the shelly log
473
562
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
474
563
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
475
564
  try {
@@ -483,6 +572,7 @@ export class Frontend extends EventEmitter {
483
572
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
484
573
  }
485
574
  });
575
+ // Endpoint to download the matterbridge log
486
576
  this.expressApp.get('/api/download-mblog', async (req, res) => {
487
577
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
488
578
  const fs = await import('node:fs');
@@ -497,12 +587,14 @@ export class Frontend extends EventEmitter {
497
587
  }
498
588
  res.type('text/plain');
499
589
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
590
+ /* istanbul ignore if */
500
591
  if (error) {
501
592
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
502
593
  res.status(500).send('Error downloading the matterbridge log file');
503
594
  }
504
595
  });
505
596
  });
597
+ // Endpoint to download the matter log
506
598
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
507
599
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
508
600
  const fs = await import('node:fs');
@@ -517,12 +609,14 @@ export class Frontend extends EventEmitter {
517
609
  }
518
610
  res.type('text/plain');
519
611
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
612
+ /* istanbul ignore if */
520
613
  if (error) {
521
614
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
522
615
  res.status(500).send('Error downloading the matter log file');
523
616
  }
524
617
  });
525
618
  });
619
+ // Endpoint to download the shelly log
526
620
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
527
621
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
528
622
  const fs = await import('node:fs');
@@ -537,75 +631,91 @@ export class Frontend extends EventEmitter {
537
631
  }
538
632
  res.type('text/plain');
539
633
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
634
+ /* istanbul ignore if */
540
635
  if (error) {
541
636
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
542
637
  res.status(500).send('Error downloading Shelly system log file');
543
638
  }
544
639
  });
545
640
  });
641
+ // Endpoint to download the matterbridge storage directory
546
642
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
547
643
  this.log.debug('The frontend sent /api/download-mbstorage');
548
644
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
549
645
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
646
+ /* istanbul ignore if */
550
647
  if (error) {
551
648
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
552
649
  res.status(500).send('Error downloading the matterbridge storage file');
553
650
  }
554
651
  });
555
652
  });
653
+ // Endpoint to download the matter storage file
556
654
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
557
655
  this.log.debug('The frontend sent /api/download-mjstorage');
558
656
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
559
657
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
658
+ /* istanbul ignore if */
560
659
  if (error) {
561
660
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
562
661
  res.status(500).send('Error downloading the matter storage zip file');
563
662
  }
564
663
  });
565
664
  });
665
+ // Endpoint to download the matterbridge plugin directory
566
666
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
567
667
  this.log.debug('The frontend sent /api/download-pluginstorage');
568
668
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
569
669
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
670
+ /* istanbul ignore if */
570
671
  if (error) {
571
672
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
572
673
  res.status(500).send('Error downloading the matterbridge plugin storage file');
573
674
  }
574
675
  });
575
676
  });
677
+ // Endpoint to download the matterbridge plugin config files
576
678
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
577
679
  this.log.debug('The frontend sent /api/download-pluginconfig');
578
680
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
579
681
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
682
+ /* istanbul ignore if */
580
683
  if (error) {
581
684
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
582
685
  res.status(500).send('Error downloading the matterbridge plugin config file');
583
686
  }
584
687
  });
585
688
  });
689
+ // Endpoint to download the matterbridge backup (created with the backup command)
586
690
  this.expressApp.get('/api/download-backup', async (req, res) => {
587
691
  this.log.debug('The frontend sent /api/download-backup');
588
692
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
693
+ /* istanbul ignore if */
589
694
  if (error) {
590
695
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
591
696
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
592
697
  }
593
698
  });
594
699
  });
700
+ // Endpoint to upload a package
595
701
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
596
702
  const { filename } = req.body;
597
703
  const file = req.file;
704
+ /* istanbul ignore if */
598
705
  if (!file || !filename) {
599
706
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
600
707
  res.status(400).send('Invalid request: file and filename are required');
601
708
  return;
602
709
  }
603
710
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
711
+ // Define the path where the plugin file will be saved
604
712
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
605
713
  try {
714
+ // Move the uploaded file to the specified path
606
715
  const fs = await import('node:fs');
607
716
  await fs.promises.rename(file.path, filePath);
608
717
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
718
+ // Install the plugin package
609
719
  if (filename.endsWith('.tgz')) {
610
720
  const { spawnCommand } = await import('./utils/spawn.js');
611
721
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -625,6 +735,7 @@ export class Frontend extends EventEmitter {
625
735
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
626
736
  }
627
737
  });
738
+ // Fallback for routing (must be the last route)
628
739
  this.expressApp.use((req, res) => {
629
740
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
630
741
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -634,13 +745,16 @@ export class Frontend extends EventEmitter {
634
745
  async stop() {
635
746
  this.log.debug('Stopping the frontend...');
636
747
  const ws = await import('ws');
748
+ // Remove listeners from the express app
637
749
  if (this.expressApp) {
638
750
  this.expressApp.removeAllListeners();
639
751
  this.expressApp = undefined;
640
752
  this.log.debug('Frontend app closed successfully');
641
753
  }
754
+ // Close the WebSocket server
642
755
  if (this.webSocketServer) {
643
756
  this.log.debug('Closing WebSocket server...');
757
+ // Close all active connections
644
758
  this.webSocketServer.clients.forEach((client) => {
645
759
  if (client.readyState === ws.WebSocket.OPEN) {
646
760
  client.close();
@@ -649,6 +763,7 @@ export class Frontend extends EventEmitter {
649
763
  await withTimeout(new Promise((resolve) => {
650
764
  this.webSocketServer?.close((error) => {
651
765
  if (error) {
766
+ // istanbul ignore next
652
767
  this.log.error(`Error closing WebSocket server: ${error}`);
653
768
  }
654
769
  else {
@@ -661,8 +776,27 @@ export class Frontend extends EventEmitter {
661
776
  this.webSocketServer.removeAllListeners();
662
777
  this.webSocketServer = undefined;
663
778
  }
779
+ // Close the http server
664
780
  if (this.httpServer) {
665
781
  this.log.debug('Closing http server...');
782
+ /*
783
+ await withTimeout(
784
+ new Promise<void>((resolve) => {
785
+ this.httpServer?.close((error) => {
786
+ if (error) {
787
+ // istanbul ignore next
788
+ this.log.error(`Error closing http server: ${error}`);
789
+ } else {
790
+ this.log.debug('Http server closed successfully');
791
+ this.emit('server_stopped');
792
+ }
793
+ resolve();
794
+ });
795
+ }),
796
+ 5000,
797
+ false,
798
+ );
799
+ */
666
800
  this.httpServer.close();
667
801
  this.log.debug('Http server closed successfully');
668
802
  this.listening = false;
@@ -671,8 +805,27 @@ export class Frontend extends EventEmitter {
671
805
  this.httpServer = undefined;
672
806
  this.log.debug('Frontend http server closed successfully');
673
807
  }
808
+ // Close the https server
674
809
  if (this.httpsServer) {
675
810
  this.log.debug('Closing https server...');
811
+ /*
812
+ await withTimeout(
813
+ new Promise<void>((resolve) => {
814
+ this.httpsServer?.close((error) => {
815
+ if (error) {
816
+ // istanbul ignore next
817
+ this.log.error(`Error closing https server: ${error}`);
818
+ } else {
819
+ this.log.debug('Https server closed successfully');
820
+ this.emit('server_stopped');
821
+ }
822
+ resolve();
823
+ });
824
+ }),
825
+ 5000,
826
+ false,
827
+ );
828
+ */
676
829
  this.httpsServer.close();
677
830
  this.log.debug('Https server closed successfully');
678
831
  this.listening = false;
@@ -683,7 +836,13 @@ export class Frontend extends EventEmitter {
683
836
  }
684
837
  this.log.debug('Frontend stopped successfully');
685
838
  }
839
+ /**
840
+ * Retrieves the api settings data.
841
+ *
842
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
843
+ */
686
844
  async getApiSettings() {
845
+ // Update the variable system information properties
687
846
  this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
688
847
  this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
689
848
  this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
@@ -693,6 +852,7 @@ export class Frontend extends EventEmitter {
693
852
  this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
694
853
  this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
695
854
  this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
855
+ // Create the matterbridge information
696
856
  const info = {
697
857
  homeDirectory: this.matterbridge.homeDirectory,
698
858
  rootDirectory: this.matterbridge.rootDirectory,
@@ -728,9 +888,15 @@ export class Frontend extends EventEmitter {
728
888
  };
729
889
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
730
890
  }
891
+ /**
892
+ * Retrieves the reachable attribute.
893
+ *
894
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
895
+ * @returns {boolean} The reachable attribute.
896
+ */
731
897
  getReachability(device) {
732
898
  if (this.matterbridge.hasCleanupStarted)
733
- return false;
899
+ return false; // Skip if cleanup has started
734
900
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
735
901
  return false;
736
902
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -741,9 +907,15 @@ export class Frontend extends EventEmitter {
741
907
  return true;
742
908
  return false;
743
909
  }
910
+ /**
911
+ * Retrieves the power source attribute.
912
+ *
913
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
914
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
915
+ */
744
916
  getPowerSource(endpoint) {
745
917
  if (this.matterbridge.hasCleanupStarted)
746
- return;
918
+ return; // Skip if cleanup has started
747
919
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
748
920
  return undefined;
749
921
  const powerSource = (device) => {
@@ -758,16 +930,25 @@ export class Frontend extends EventEmitter {
758
930
  }
759
931
  return;
760
932
  };
933
+ // Root endpoint
761
934
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
762
935
  return powerSource(endpoint);
936
+ // Child endpoints
763
937
  for (const child of endpoint.getChildEndpoints()) {
764
938
  if (child.hasClusterServer(PowerSource.Cluster.id))
765
939
  return powerSource(child);
766
940
  }
767
941
  }
942
+ /**
943
+ * Retrieves the cluster text description from a given device.
944
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
945
+ *
946
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
947
+ * @returns {string} The attributes description of the cluster servers in the device.
948
+ */
768
949
  getClusterTextFromDevice(device) {
769
950
  if (this.matterbridge.hasCleanupStarted)
770
- return '';
951
+ return ''; // Skip if cleanup has started
771
952
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
772
953
  return '';
773
954
  const getUserLabel = (device) => {
@@ -777,6 +958,7 @@ export class Frontend extends EventEmitter {
777
958
  if (composed)
778
959
  return 'Composed: ' + composed.value;
779
960
  }
961
+ // istanbul ignore next cause is not reachable
780
962
  return '';
781
963
  };
782
964
  const getFixedLabel = (device) => {
@@ -786,11 +968,13 @@ export class Frontend extends EventEmitter {
786
968
  if (composed)
787
969
  return 'Composed: ' + composed.value;
788
970
  }
971
+ // istanbul ignore next cause is not reacheable
789
972
  return '';
790
973
  };
791
974
  let attributes = '';
792
975
  let supportedModes = [];
793
976
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
977
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
794
978
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
795
979
  return;
796
980
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -880,11 +1064,17 @@ export class Frontend extends EventEmitter {
880
1064
  if (clusterName === 'userLabel' && attributeName === 'labelList')
881
1065
  attributes += `${getUserLabel(device)} `;
882
1066
  });
1067
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
883
1068
  return attributes.trimStart().trimEnd();
884
1069
  }
1070
+ /**
1071
+ * Retrieves the registered plugins sanitized for res.json().
1072
+ *
1073
+ * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1074
+ */
885
1075
  getPlugins() {
886
1076
  if (this.matterbridge.hasCleanupStarted)
887
- return [];
1077
+ return []; // Skip if cleanup has started
888
1078
  const plugins = [];
889
1079
  for (const plugin of this.matterbridge.plugins.array()) {
890
1080
  plugins.push({
@@ -912,18 +1102,27 @@ export class Frontend extends EventEmitter {
912
1102
  schemaJson: plugin.schemaJson,
913
1103
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
914
1104
  hasBlackList: plugin.configJson?.blackList !== undefined,
1105
+ // Childbridge mode specific data
915
1106
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
916
1107
  });
917
1108
  }
918
1109
  return plugins;
919
1110
  }
1111
+ /**
1112
+ * Retrieves the devices from Matterbridge.
1113
+ *
1114
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1115
+ * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1116
+ */
920
1117
  getDevices(pluginName) {
921
1118
  if (this.matterbridge.hasCleanupStarted)
922
- return [];
1119
+ return []; // Skip if cleanup has started
923
1120
  const devices = [];
924
1121
  for (const device of this.matterbridge.devices.array()) {
1122
+ // Filter by pluginName if provided
925
1123
  if (pluginName && pluginName !== device.plugin)
926
1124
  continue;
1125
+ // Check if the device has the required properties
927
1126
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
928
1127
  continue;
929
1128
  devices.push({
@@ -943,24 +1142,39 @@ export class Frontend extends EventEmitter {
943
1142
  }
944
1143
  return devices;
945
1144
  }
1145
+ /**
1146
+ * Retrieves the clusters from a given plugin and endpoint number.
1147
+ *
1148
+ * Response for /api/clusters
1149
+ *
1150
+ * @param {string} pluginName - The name of the plugin.
1151
+ * @param {number} endpointNumber - The endpoint number.
1152
+ * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1153
+ */
946
1154
  getClusters(pluginName, endpointNumber) {
947
1155
  if (this.matterbridge.hasCleanupStarted)
948
- return;
1156
+ return; // Skip if cleanup has started
949
1157
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
950
1158
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
951
1159
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
952
1160
  return;
953
1161
  }
1162
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1163
+ // Get the device types from the main endpoint
954
1164
  const deviceTypes = [];
955
1165
  const clusters = [];
956
1166
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
957
1167
  deviceTypes.push(d.deviceType);
958
1168
  });
1169
+ // Get the clusters from the main endpoint
959
1170
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
960
1171
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
961
1172
  return;
962
1173
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
963
1174
  return;
1175
+ // console.log(
1176
+ // `${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}`,
1177
+ // );
964
1178
  clusters.push({
965
1179
  endpoint: endpoint.number.toString(),
966
1180
  number: endpoint.number,
@@ -974,12 +1188,19 @@ export class Frontend extends EventEmitter {
974
1188
  attributeLocalValue: attributeValue,
975
1189
  });
976
1190
  });
1191
+ // Get the child endpoints
977
1192
  const childEndpoints = endpoint.getChildEndpoints();
1193
+ // if (childEndpoints.length === 0) {
1194
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1195
+ // }
978
1196
  childEndpoints.forEach((childEndpoint) => {
1197
+ // istanbul ignore if cause is not reachable: should never happen but ...
979
1198
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
980
1199
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
981
1200
  return;
982
1201
  }
1202
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1203
+ // Get the device types of the child endpoint
983
1204
  const deviceTypes = [];
984
1205
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
985
1206
  deviceTypes.push(d.deviceType);
@@ -989,6 +1210,9 @@ export class Frontend extends EventEmitter {
989
1210
  return;
990
1211
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
991
1212
  return;
1213
+ // console.log(
1214
+ // `${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}`,
1215
+ // );
992
1216
  clusters.push({
993
1217
  endpoint: childEndpoint.number.toString(),
994
1218
  number: childEndpoint.number,
@@ -1008,6 +1232,7 @@ export class Frontend extends EventEmitter {
1008
1232
  async generateDiagnostic() {
1009
1233
  this.log.debug('Generating diagnostic...');
1010
1234
  const serverNodes = [];
1235
+ // istanbul ignore else
1011
1236
  if (this.matterbridge.bridgeMode === 'bridge') {
1012
1237
  if (this.matterbridge.serverNode)
1013
1238
  serverNodes.push(this.matterbridge.serverNode);
@@ -1018,6 +1243,7 @@ export class Frontend extends EventEmitter {
1018
1243
  serverNodes.push(plugin.serverNode);
1019
1244
  }
1020
1245
  }
1246
+ // istanbul ignore next
1021
1247
  for (const device of this.matterbridge.devices.array()) {
1022
1248
  if (device.serverNode)
1023
1249
  serverNodes.push(device.serverNode);
@@ -1041,8 +1267,15 @@ export class Frontend extends EventEmitter {
1041
1267
  values: [...serverNodes],
1042
1268
  })));
1043
1269
  delete Logger.destinations.diagnostic;
1044
- await wait(500);
1270
+ await wait(500); // Wait for the log to be written
1045
1271
  }
1272
+ /**
1273
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1274
+ *
1275
+ * @param {WebSocket} client - The websocket client that sent the message.
1276
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1277
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1278
+ */
1046
1279
  async wsMessageHandler(client, message) {
1047
1280
  let data;
1048
1281
  const sendResponse = (data) => {
@@ -1062,7 +1295,7 @@ export class Frontend extends EventEmitter {
1062
1295
  };
1063
1296
  try {
1064
1297
  data = JSON.parse(message.toString());
1065
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1298
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1066
1299
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1067
1300
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1068
1301
  return;
@@ -1136,6 +1369,7 @@ export class Frontend extends EventEmitter {
1136
1369
  return;
1137
1370
  })
1138
1371
  .catch((_error) => {
1372
+ //
1139
1373
  });
1140
1374
  }
1141
1375
  else {
@@ -1183,6 +1417,7 @@ export class Frontend extends EventEmitter {
1183
1417
  return;
1184
1418
  })
1185
1419
  .catch((_error) => {
1420
+ //
1186
1421
  });
1187
1422
  }
1188
1423
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1208,6 +1443,7 @@ export class Frontend extends EventEmitter {
1208
1443
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1209
1444
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1210
1445
  if (plugin.serverNode) {
1446
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1211
1447
  await this.matterbridge.stopServerNode(plugin.serverNode);
1212
1448
  plugin.serverNode = undefined;
1213
1449
  }
@@ -1217,18 +1453,20 @@ export class Frontend extends EventEmitter {
1217
1453
  this.matterbridge.devices.remove(device);
1218
1454
  }
1219
1455
  }
1456
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1220
1457
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1221
1458
  await this.matterbridge.createDynamicPlugin(plugin);
1222
1459
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1223
- plugin.restartRequired = false;
1460
+ plugin.restartRequired = false; // Reset plugin restartRequired
1224
1461
  let needRestart = 0;
1225
1462
  for (const plugin of this.matterbridge.plugins) {
1226
1463
  if (plugin.restartRequired)
1227
1464
  needRestart++;
1228
1465
  }
1229
1466
  if (needRestart === 0) {
1230
- this.wssSendRestartNotRequired(true);
1467
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1231
1468
  }
1469
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1232
1470
  if (plugin.serverNode)
1233
1471
  await this.matterbridge.startServerNode(plugin.serverNode);
1234
1472
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1493,22 +1731,22 @@ export class Frontend extends EventEmitter {
1493
1731
  if (isValidString(data.params.value, 4)) {
1494
1732
  this.log.debug('Matterbridge logger level:', data.params.value);
1495
1733
  if (data.params.value === 'Debug') {
1496
- await this.matterbridge.setLogLevel("debug");
1734
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1497
1735
  }
1498
1736
  else if (data.params.value === 'Info') {
1499
- await this.matterbridge.setLogLevel("info");
1737
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1500
1738
  }
1501
1739
  else if (data.params.value === 'Notice') {
1502
- await this.matterbridge.setLogLevel("notice");
1740
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1503
1741
  }
1504
1742
  else if (data.params.value === 'Warn') {
1505
- await this.matterbridge.setLogLevel("warn");
1743
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1506
1744
  }
1507
1745
  else if (data.params.value === 'Error') {
1508
- await this.matterbridge.setLogLevel("error");
1746
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1509
1747
  }
1510
1748
  else if (data.params.value === 'Fatal') {
1511
- await this.matterbridge.setLogLevel("fatal");
1749
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1512
1750
  }
1513
1751
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1514
1752
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1519,6 +1757,7 @@ export class Frontend extends EventEmitter {
1519
1757
  this.log.debug('Matterbridge file log:', data.params.value);
1520
1758
  this.matterbridge.fileLogger = data.params.value;
1521
1759
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1760
+ // Create the file logger for matterbridge
1522
1761
  if (data.params.value)
1523
1762
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1524
1763
  else
@@ -1547,6 +1786,14 @@ export class Frontend extends EventEmitter {
1547
1786
  else if (data.params.value === 'Fatal') {
1548
1787
  Logger.level = MatterLogLevel.FATAL;
1549
1788
  }
1789
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1790
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1791
+ if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1792
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1793
+ if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1794
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1795
+ AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1796
+ this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1550
1797
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
1551
1798
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1552
1799
  }
@@ -1596,6 +1843,7 @@ export class Frontend extends EventEmitter {
1596
1843
  }
1597
1844
  break;
1598
1845
  case 'setmatterport':
1846
+ // eslint-disable-next-line no-case-declarations
1599
1847
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1600
1848
  if (isValidNumber(port, 5540, 5600)) {
1601
1849
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1615,6 +1863,7 @@ export class Frontend extends EventEmitter {
1615
1863
  }
1616
1864
  break;
1617
1865
  case 'setmatterdiscriminator':
1866
+ // eslint-disable-next-line no-case-declarations
1618
1867
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1619
1868
  if (isValidNumber(discriminator, 0, 4095)) {
1620
1869
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1634,6 +1883,7 @@ export class Frontend extends EventEmitter {
1634
1883
  }
1635
1884
  break;
1636
1885
  case 'setmatterpasscode':
1886
+ // eslint-disable-next-line no-case-declarations
1637
1887
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1638
1888
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1639
1889
  this.matterbridge.passcode = passcode;
@@ -1679,15 +1929,19 @@ export class Frontend extends EventEmitter {
1679
1929
  return;
1680
1930
  }
1681
1931
  const config = plugin.configJson;
1932
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1682
1933
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1934
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1683
1935
  if (select === 'serial')
1684
1936
  this.log.info(`Selected device serial ${data.params.serial}`);
1685
1937
  if (select === 'name')
1686
1938
  this.log.info(`Selected device name ${data.params.name}`);
1687
1939
  if (config && select && (select === 'serial' || select === 'name')) {
1940
+ // Remove postfix from the serial if it exists
1688
1941
  if (config.postfix) {
1689
1942
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1690
1943
  }
1944
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1691
1945
  if (isValidArray(config.whiteList, 1)) {
1692
1946
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1693
1947
  config.whiteList.push(data.params.serial);
@@ -1696,6 +1950,7 @@ export class Frontend extends EventEmitter {
1696
1950
  config.whiteList.push(data.params.name);
1697
1951
  }
1698
1952
  }
1953
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1699
1954
  if (isValidArray(config.blackList, 1)) {
1700
1955
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1701
1956
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1723,7 +1978,9 @@ export class Frontend extends EventEmitter {
1723
1978
  return;
1724
1979
  }
1725
1980
  const config = plugin.configJson;
1981
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1726
1982
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1983
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1727
1984
  if (select === 'serial')
1728
1985
  this.log.info(`Unselected device serial ${data.params.serial}`);
1729
1986
  if (select === 'name')
@@ -1732,6 +1989,7 @@ export class Frontend extends EventEmitter {
1732
1989
  if (config.postfix) {
1733
1990
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1734
1991
  }
1992
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1735
1993
  if (isValidArray(config.whiteList, 1)) {
1736
1994
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1737
1995
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1740,6 +1998,7 @@ export class Frontend extends EventEmitter {
1740
1998
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1741
1999
  }
1742
2000
  }
2001
+ // Add the serial to the blackList
1743
2002
  if (isValidArray(config.blackList)) {
1744
2003
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1745
2004
  config.blackList.push(data.params.serial);
@@ -1762,6 +2021,7 @@ export class Frontend extends EventEmitter {
1762
2021
  }
1763
2022
  }
1764
2023
  else {
2024
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1765
2025
  const localData = data;
1766
2026
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1767
2027
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1771,23 +2031,46 @@ export class Frontend extends EventEmitter {
1771
2031
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1772
2032
  }
1773
2033
  }
2034
+ /**
2035
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2036
+ *
2037
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2038
+ * @param {string} time - The time string of the message
2039
+ * @param {string} name - The logger name of the message
2040
+ * @param {string} message - The content of the message.
2041
+ *
2042
+ * @remarks
2043
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2044
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2045
+ * The function sends the message to all connected clients.
2046
+ */
1774
2047
  wssSendLogMessage(level, time, name, message) {
1775
2048
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1776
2049
  return;
1777
2050
  if (!level || !time || !name || !message)
1778
2051
  return;
2052
+ // Remove ANSI escape codes from the message
2053
+ // eslint-disable-next-line no-control-regex
1779
2054
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2055
+ // Remove leading asterisks from the message
1780
2056
  message = message.replace(/^\*+/, '');
2057
+ // Replace all occurrences of \t and \n
1781
2058
  message = message.replace(/[\t\n]/g, '');
2059
+ // Remove non-printable characters
2060
+ // eslint-disable-next-line no-control-regex
1782
2061
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2062
+ // Replace all occurrences of \" with "
1783
2063
  message = message.replace(/\\"/g, '"');
2064
+ // Define the maximum allowed length for continuous characters without a space
1784
2065
  const maxContinuousLength = 100;
1785
2066
  const keepStartLength = 20;
1786
2067
  const keepEndLength = 20;
2068
+ // Split the message into words
1787
2069
  if (level !== 'spawn') {
1788
2070
  message = message
1789
2071
  .split(' ')
1790
2072
  .map((word) => {
2073
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1791
2074
  if (word.length > maxContinuousLength) {
1792
2075
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1793
2076
  }
@@ -1795,14 +2078,34 @@ export class Frontend extends EventEmitter {
1795
2078
  })
1796
2079
  .join(' ');
1797
2080
  }
2081
+ // Send the message to all connected clients
1798
2082
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
1799
2083
  }
2084
+ /**
2085
+ * Sends a need to refresh WebSocket message to all connected clients.
2086
+ *
2087
+ * @param {string} changed - The changed value.
2088
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2089
+ * possible values for changed:
2090
+ * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2091
+ * - 'plugins'
2092
+ * - 'devices'
2093
+ * - 'matter' with param 'matter' (QRDiv component)
2094
+ * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2095
+ */
1800
2096
  wssSendRefreshRequired(changed, params) {
1801
2097
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1802
2098
  return;
1803
2099
  this.log.debug('Sending a refresh required message to all connected clients');
2100
+ // Send the message to all connected clients
1804
2101
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
1805
2102
  }
2103
+ /**
2104
+ * Sends a need to restart WebSocket message to all connected clients.
2105
+ *
2106
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2107
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2108
+ */
1806
2109
  wssSendRestartRequired(snackbar = true, fixed = false) {
1807
2110
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1808
2111
  return;
@@ -1811,8 +2114,14 @@ export class Frontend extends EventEmitter {
1811
2114
  this.matterbridge.fixedRestartRequired = fixed;
1812
2115
  if (snackbar === true)
1813
2116
  this.wssSendSnackbarMessage(`Restart required`, 0);
2117
+ // Send the message to all connected clients
1814
2118
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1815
2119
  }
2120
+ /**
2121
+ * Sends a no need to restart WebSocket message to all connected clients.
2122
+ *
2123
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2124
+ */
1816
2125
  wssSendRestartNotRequired(snackbar = true) {
1817
2126
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1818
2127
  return;
@@ -1820,57 +2129,133 @@ export class Frontend extends EventEmitter {
1820
2129
  this.matterbridge.restartRequired = false;
1821
2130
  if (snackbar === true)
1822
2131
  this.wssSendCloseSnackbarMessage(`Restart required`);
2132
+ // Send the message to all connected clients
1823
2133
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1824
2134
  }
2135
+ /**
2136
+ * Sends a need to update WebSocket message to all connected clients.
2137
+ *
2138
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2139
+ */
1825
2140
  wssSendUpdateRequired(devVersion = false) {
1826
2141
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1827
2142
  return;
1828
2143
  this.log.debug('Sending an update required message to all connected clients');
1829
2144
  this.matterbridge.updateRequired = true;
2145
+ // Send the message to all connected clients
1830
2146
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1831
2147
  }
2148
+ /**
2149
+ * Sends a cpu update message to all connected clients.
2150
+ *
2151
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2152
+ * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2153
+ */
1832
2154
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
1833
2155
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1834
2156
  return;
1835
2157
  if (hasParameter('debug'))
1836
2158
  this.log.debug('Sending a cpu update message to all connected clients');
2159
+ // Send the message to all connected clients
1837
2160
  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 } });
1838
2161
  }
2162
+ /**
2163
+ * Sends a memory update message to all connected clients.
2164
+ *
2165
+ * @param {string} totalMemory - The total memory in bytes.
2166
+ * @param {string} freeMemory - The free memory in bytes.
2167
+ * @param {string} rss - The resident set size in bytes.
2168
+ * @param {string} heapTotal - The total heap memory in bytes.
2169
+ * @param {string} heapUsed - The used heap memory in bytes.
2170
+ * @param {string} external - The external memory in bytes.
2171
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2172
+ */
1839
2173
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1840
2174
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1841
2175
  return;
1842
2176
  if (hasParameter('debug'))
1843
2177
  this.log.debug('Sending a memory update message to all connected clients');
2178
+ // Send the message to all connected clients
1844
2179
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1845
2180
  }
2181
+ /**
2182
+ * Sends an uptime update message to all connected clients.
2183
+ *
2184
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2185
+ * @param {string} processUptime - The process uptime in a human-readable format.
2186
+ */
1846
2187
  wssSendUptimeUpdate(systemUptime, processUptime) {
1847
2188
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1848
2189
  return;
1849
2190
  if (hasParameter('debug'))
1850
2191
  this.log.debug('Sending a uptime update message to all connected clients');
2192
+ // Send the message to all connected clients
1851
2193
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
1852
2194
  }
2195
+ /**
2196
+ * Sends an open snackbar message to all connected clients.
2197
+ *
2198
+ * @param {string} message - The message to send.
2199
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2200
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2201
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2202
+ *
2203
+ * @remarks
2204
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2205
+ */
1853
2206
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1854
2207
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1855
2208
  return;
1856
2209
  this.log.debug('Sending a snackbar message to all connected clients');
2210
+ // Send the message to all connected clients
1857
2211
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
1858
2212
  }
2213
+ /**
2214
+ * Sends a close snackbar message to all connected clients.
2215
+ * It will close the snackbar message with the same message and timeout = 0.
2216
+ *
2217
+ * @param {string} message - The message to send.
2218
+ */
1859
2219
  wssSendCloseSnackbarMessage(message) {
1860
2220
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1861
2221
  return;
1862
2222
  this.log.debug('Sending a close snackbar message to all connected clients');
2223
+ // Send the message to all connected clients
1863
2224
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1864
2225
  }
2226
+ /**
2227
+ * Sends an attribute update message to all connected WebSocket clients.
2228
+ *
2229
+ * @param {string | undefined} plugin - The name of the plugin.
2230
+ * @param {string | undefined} serialNumber - The serial number of the device.
2231
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2232
+ * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2233
+ * @param {string} id - The endpoint id where the attribute belongs.
2234
+ * @param {string} cluster - The cluster name where the attribute belongs.
2235
+ * @param {string} attribute - The name of the attribute that changed.
2236
+ * @param {number | string | boolean} value - The new value of the attribute.
2237
+ *
2238
+ * @remarks
2239
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2240
+ * with the updated attribute information.
2241
+ */
1865
2242
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1866
2243
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1867
2244
  return;
1868
2245
  this.log.debug('Sending an attribute update message to all connected clients');
2246
+ // Send the message to all connected clients
1869
2247
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1870
2248
  }
2249
+ /**
2250
+ * Sends a message to all connected clients.
2251
+ * This is an helper function to send a broadcast message to all connected clients.
2252
+ *
2253
+ * @param {WsMessageBroadcast} msg - The message to send.
2254
+ */
1871
2255
  wssBroadcastMessage(msg) {
1872
2256
  if (!this.listening || this.webSocketServer?.clients.size === 0)
1873
2257
  return;
2258
+ // Send the message to all connected clients
1874
2259
  const stringifiedMsg = JSON.stringify(msg);
1875
2260
  if (msg.method !== 'log')
1876
2261
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -1881,3 +2266,4 @@ export class Frontend extends EventEmitter {
1881
2266
  });
1882
2267
  }
1883
2268
  }
2269
+ //# sourceMappingURL=frontend.js.map