matterbridge 3.2.9-dev-20250926-85736bb → 3.2.9

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 (284) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/cli.d.ts +26 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +91 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cliEmitter.d.ts +34 -0
  7. package/dist/cliEmitter.d.ts.map +1 -0
  8. package/dist/cliEmitter.js +30 -0
  9. package/dist/cliEmitter.js.map +1 -0
  10. package/dist/clusters/export.d.ts +2 -0
  11. package/dist/clusters/export.d.ts.map +1 -0
  12. package/dist/clusters/export.js +2 -0
  13. package/dist/clusters/export.js.map +1 -0
  14. package/dist/defaultConfigSchema.d.ts +28 -0
  15. package/dist/defaultConfigSchema.d.ts.map +1 -0
  16. package/dist/defaultConfigSchema.js +24 -0
  17. package/dist/defaultConfigSchema.js.map +1 -0
  18. package/dist/deviceManager.d.ts +112 -0
  19. package/dist/deviceManager.d.ts.map +1 -0
  20. package/dist/deviceManager.js +94 -1
  21. package/dist/deviceManager.js.map +1 -0
  22. package/dist/devices/airConditioner.d.ts +98 -0
  23. package/dist/devices/airConditioner.d.ts.map +1 -0
  24. package/dist/devices/airConditioner.js +57 -0
  25. package/dist/devices/airConditioner.js.map +1 -0
  26. package/dist/devices/batteryStorage.d.ts +48 -0
  27. package/dist/devices/batteryStorage.d.ts.map +1 -0
  28. package/dist/devices/batteryStorage.js +48 -1
  29. package/dist/devices/batteryStorage.js.map +1 -0
  30. package/dist/devices/cooktop.d.ts +60 -0
  31. package/dist/devices/cooktop.d.ts.map +1 -0
  32. package/dist/devices/cooktop.js +55 -0
  33. package/dist/devices/cooktop.js.map +1 -0
  34. package/dist/devices/dishwasher.d.ts +71 -0
  35. package/dist/devices/dishwasher.d.ts.map +1 -0
  36. package/dist/devices/dishwasher.js +57 -0
  37. package/dist/devices/dishwasher.js.map +1 -0
  38. package/dist/devices/evse.d.ts +75 -0
  39. package/dist/devices/evse.d.ts.map +1 -0
  40. package/dist/devices/evse.js +74 -10
  41. package/dist/devices/evse.js.map +1 -0
  42. package/dist/devices/export.d.ts +17 -0
  43. package/dist/devices/export.d.ts.map +1 -0
  44. package/dist/devices/export.js +5 -0
  45. package/dist/devices/export.js.map +1 -0
  46. package/dist/devices/extractorHood.d.ts +46 -0
  47. package/dist/devices/extractorHood.d.ts.map +1 -0
  48. package/dist/devices/extractorHood.js +42 -0
  49. package/dist/devices/extractorHood.js.map +1 -0
  50. package/dist/devices/heatPump.d.ts +47 -0
  51. package/dist/devices/heatPump.d.ts.map +1 -0
  52. package/dist/devices/heatPump.js +50 -2
  53. package/dist/devices/heatPump.js.map +1 -0
  54. package/dist/devices/laundryDryer.d.ts +67 -0
  55. package/dist/devices/laundryDryer.d.ts.map +1 -0
  56. package/dist/devices/laundryDryer.js +62 -3
  57. package/dist/devices/laundryDryer.js.map +1 -0
  58. package/dist/devices/laundryWasher.d.ts +81 -0
  59. package/dist/devices/laundryWasher.d.ts.map +1 -0
  60. package/dist/devices/laundryWasher.js +70 -4
  61. package/dist/devices/laundryWasher.js.map +1 -0
  62. package/dist/devices/microwaveOven.d.ts +168 -0
  63. package/dist/devices/microwaveOven.d.ts.map +1 -0
  64. package/dist/devices/microwaveOven.js +88 -5
  65. package/dist/devices/microwaveOven.js.map +1 -0
  66. package/dist/devices/oven.d.ts +105 -0
  67. package/dist/devices/oven.d.ts.map +1 -0
  68. package/dist/devices/oven.js +85 -0
  69. package/dist/devices/oven.js.map +1 -0
  70. package/dist/devices/refrigerator.d.ts +118 -0
  71. package/dist/devices/refrigerator.d.ts.map +1 -0
  72. package/dist/devices/refrigerator.js +102 -0
  73. package/dist/devices/refrigerator.js.map +1 -0
  74. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  75. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  76. package/dist/devices/roboticVacuumCleaner.js +100 -9
  77. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  78. package/dist/devices/solarPower.d.ts +40 -0
  79. package/dist/devices/solarPower.d.ts.map +1 -0
  80. package/dist/devices/solarPower.js +38 -0
  81. package/dist/devices/solarPower.js.map +1 -0
  82. package/dist/devices/speaker.d.ts +87 -0
  83. package/dist/devices/speaker.d.ts.map +1 -0
  84. package/dist/devices/speaker.js +84 -0
  85. package/dist/devices/speaker.js.map +1 -0
  86. package/dist/devices/temperatureControl.d.ts +166 -0
  87. package/dist/devices/temperatureControl.d.ts.map +1 -0
  88. package/dist/devices/temperatureControl.js +25 -3
  89. package/dist/devices/temperatureControl.js.map +1 -0
  90. package/dist/devices/waterHeater.d.ts +111 -0
  91. package/dist/devices/waterHeater.d.ts.map +1 -0
  92. package/dist/devices/waterHeater.js +82 -2
  93. package/dist/devices/waterHeater.js.map +1 -0
  94. package/dist/dgram/coap.d.ts +205 -0
  95. package/dist/dgram/coap.d.ts.map +1 -0
  96. package/dist/dgram/coap.js +126 -13
  97. package/dist/dgram/coap.js.map +1 -0
  98. package/dist/dgram/dgram.d.ts +141 -0
  99. package/dist/dgram/dgram.d.ts.map +1 -0
  100. package/dist/dgram/dgram.js +114 -2
  101. package/dist/dgram/dgram.js.map +1 -0
  102. package/dist/dgram/mb_coap.d.ts +24 -0
  103. package/dist/dgram/mb_coap.d.ts.map +1 -0
  104. package/dist/dgram/mb_coap.js +41 -3
  105. package/dist/dgram/mb_coap.js.map +1 -0
  106. package/dist/dgram/mb_mdns.d.ts +24 -0
  107. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  108. package/dist/dgram/mb_mdns.js +80 -15
  109. package/dist/dgram/mb_mdns.js.map +1 -0
  110. package/dist/dgram/mdns.d.ts +290 -0
  111. package/dist/dgram/mdns.d.ts.map +1 -0
  112. package/dist/dgram/mdns.js +299 -137
  113. package/dist/dgram/mdns.js.map +1 -0
  114. package/dist/dgram/multicast.d.ts +67 -0
  115. package/dist/dgram/multicast.d.ts.map +1 -0
  116. package/dist/dgram/multicast.js +62 -1
  117. package/dist/dgram/multicast.js.map +1 -0
  118. package/dist/dgram/unicast.d.ts +56 -0
  119. package/dist/dgram/unicast.d.ts.map +1 -0
  120. package/dist/dgram/unicast.js +54 -0
  121. package/dist/dgram/unicast.js.map +1 -0
  122. package/dist/frontend.d.ts +232 -0
  123. package/dist/frontend.d.ts.map +1 -0
  124. package/dist/frontend.js +428 -34
  125. package/dist/frontend.js.map +1 -0
  126. package/dist/frontendTypes.d.ts +514 -0
  127. package/dist/frontendTypes.d.ts.map +1 -0
  128. package/dist/frontendTypes.js +45 -0
  129. package/dist/frontendTypes.js.map +1 -0
  130. package/dist/globalMatterbridge.d.ts +59 -0
  131. package/dist/globalMatterbridge.d.ts.map +1 -0
  132. package/dist/globalMatterbridge.js +47 -0
  133. package/dist/globalMatterbridge.js.map +1 -0
  134. package/dist/helpers.d.ts +48 -0
  135. package/dist/helpers.d.ts.map +1 -0
  136. package/dist/helpers.js +53 -0
  137. package/dist/helpers.js.map +1 -0
  138. package/dist/index.d.ts +33 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +30 -1
  141. package/dist/index.js.map +1 -0
  142. package/dist/logger/export.d.ts +2 -0
  143. package/dist/logger/export.d.ts.map +1 -0
  144. package/dist/logger/export.js +1 -0
  145. package/dist/logger/export.js.map +1 -0
  146. package/dist/matter/behaviors.d.ts +2 -0
  147. package/dist/matter/behaviors.d.ts.map +1 -0
  148. package/dist/matter/behaviors.js +2 -0
  149. package/dist/matter/behaviors.js.map +1 -0
  150. package/dist/matter/clusters.d.ts +2 -0
  151. package/dist/matter/clusters.d.ts.map +1 -0
  152. package/dist/matter/clusters.js +2 -0
  153. package/dist/matter/clusters.js.map +1 -0
  154. package/dist/matter/devices.d.ts +2 -0
  155. package/dist/matter/devices.d.ts.map +1 -0
  156. package/dist/matter/devices.js +2 -0
  157. package/dist/matter/devices.js.map +1 -0
  158. package/dist/matter/endpoints.d.ts +2 -0
  159. package/dist/matter/endpoints.d.ts.map +1 -0
  160. package/dist/matter/endpoints.js +2 -0
  161. package/dist/matter/endpoints.js.map +1 -0
  162. package/dist/matter/export.d.ts +5 -0
  163. package/dist/matter/export.d.ts.map +1 -0
  164. package/dist/matter/export.js +3 -0
  165. package/dist/matter/export.js.map +1 -0
  166. package/dist/matter/types.d.ts +3 -0
  167. package/dist/matter/types.d.ts.map +1 -0
  168. package/dist/matter/types.js +3 -0
  169. package/dist/matter/types.js.map +1 -0
  170. package/dist/matterbridge.d.ts +444 -0
  171. package/dist/matterbridge.d.ts.map +1 -0
  172. package/dist/matterbridge.js +784 -51
  173. package/dist/matterbridge.js.map +1 -0
  174. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  175. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  176. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  177. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  178. package/dist/matterbridgeBehaviors.d.ts +1747 -0
  179. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  180. package/dist/matterbridgeBehaviors.js +65 -5
  181. package/dist/matterbridgeBehaviors.js.map +1 -0
  182. package/dist/matterbridgeDeviceTypes.d.ts +761 -0
  183. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  184. package/dist/matterbridgeDeviceTypes.js +630 -17
  185. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  186. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  187. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  188. package/dist/matterbridgeDynamicPlatform.js +36 -0
  189. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  190. package/dist/matterbridgeEndpoint.d.ts +1534 -0
  191. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  192. package/dist/matterbridgeEndpoint.js +1398 -58
  193. package/dist/matterbridgeEndpoint.js.map +1 -0
  194. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  195. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  196. package/dist/matterbridgeEndpointHelpers.js +345 -12
  197. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  198. package/dist/matterbridgePlatform.d.ts +365 -0
  199. package/dist/matterbridgePlatform.d.ts.map +1 -0
  200. package/dist/matterbridgePlatform.js +304 -0
  201. package/dist/matterbridgePlatform.js.map +1 -0
  202. package/dist/matterbridgeTypes.d.ts +197 -0
  203. package/dist/matterbridgeTypes.d.ts.map +1 -0
  204. package/dist/matterbridgeTypes.js +25 -0
  205. package/dist/matterbridgeTypes.js.map +1 -0
  206. package/dist/pluginManager.d.ts +270 -0
  207. package/dist/pluginManager.d.ts.map +1 -0
  208. package/dist/pluginManager.js +249 -3
  209. package/dist/pluginManager.js.map +1 -0
  210. package/dist/shelly.d.ts +174 -0
  211. package/dist/shelly.d.ts.map +1 -0
  212. package/dist/shelly.js +168 -7
  213. package/dist/shelly.js.map +1 -0
  214. package/dist/storage/export.d.ts +2 -0
  215. package/dist/storage/export.d.ts.map +1 -0
  216. package/dist/storage/export.js +1 -0
  217. package/dist/storage/export.js.map +1 -0
  218. package/dist/update.d.ts +75 -0
  219. package/dist/update.d.ts.map +1 -0
  220. package/dist/update.js +69 -0
  221. package/dist/update.js.map +1 -0
  222. package/dist/utils/colorUtils.d.ts +99 -0
  223. package/dist/utils/colorUtils.d.ts.map +1 -0
  224. package/dist/utils/colorUtils.js +97 -2
  225. package/dist/utils/colorUtils.js.map +1 -0
  226. package/dist/utils/commandLine.d.ts +59 -0
  227. package/dist/utils/commandLine.d.ts.map +1 -0
  228. package/dist/utils/commandLine.js +54 -0
  229. package/dist/utils/commandLine.js.map +1 -0
  230. package/dist/utils/copyDirectory.d.ts +33 -0
  231. package/dist/utils/copyDirectory.d.ts.map +1 -0
  232. package/dist/utils/copyDirectory.js +38 -1
  233. package/dist/utils/copyDirectory.js.map +1 -0
  234. package/dist/utils/createDirectory.d.ts +34 -0
  235. package/dist/utils/createDirectory.d.ts.map +1 -0
  236. package/dist/utils/createDirectory.js +33 -0
  237. package/dist/utils/createDirectory.js.map +1 -0
  238. package/dist/utils/createZip.d.ts +39 -0
  239. package/dist/utils/createZip.d.ts.map +1 -0
  240. package/dist/utils/createZip.js +47 -2
  241. package/dist/utils/createZip.js.map +1 -0
  242. package/dist/utils/deepCopy.d.ts +32 -0
  243. package/dist/utils/deepCopy.d.ts.map +1 -0
  244. package/dist/utils/deepCopy.js +39 -0
  245. package/dist/utils/deepCopy.js.map +1 -0
  246. package/dist/utils/deepEqual.d.ts +54 -0
  247. package/dist/utils/deepEqual.d.ts.map +1 -0
  248. package/dist/utils/deepEqual.js +72 -1
  249. package/dist/utils/deepEqual.js.map +1 -0
  250. package/dist/utils/error.d.ts +44 -0
  251. package/dist/utils/error.d.ts.map +1 -0
  252. package/dist/utils/error.js +41 -0
  253. package/dist/utils/error.js.map +1 -0
  254. package/dist/utils/export.d.ts +13 -0
  255. package/dist/utils/export.d.ts.map +1 -0
  256. package/dist/utils/export.js +1 -0
  257. package/dist/utils/export.js.map +1 -0
  258. package/dist/utils/hex.d.ts +89 -0
  259. package/dist/utils/hex.d.ts.map +1 -0
  260. package/dist/utils/hex.js +124 -0
  261. package/dist/utils/hex.js.map +1 -0
  262. package/dist/utils/isvalid.d.ts +103 -0
  263. package/dist/utils/isvalid.d.ts.map +1 -0
  264. package/dist/utils/isvalid.js +101 -0
  265. package/dist/utils/isvalid.js.map +1 -0
  266. package/dist/utils/jestHelpers.d.ts +137 -0
  267. package/dist/utils/jestHelpers.d.ts.map +1 -0
  268. package/dist/utils/jestHelpers.js +153 -3
  269. package/dist/utils/jestHelpers.js.map +1 -0
  270. package/dist/utils/network.d.ts +84 -0
  271. package/dist/utils/network.d.ts.map +1 -0
  272. package/dist/utils/network.js +91 -5
  273. package/dist/utils/network.js.map +1 -0
  274. package/dist/utils/spawn.d.ts +34 -0
  275. package/dist/utils/spawn.d.ts.map +1 -0
  276. package/dist/utils/spawn.js +84 -6
  277. package/dist/utils/spawn.js.map +1 -0
  278. package/dist/utils/wait.d.ts +54 -0
  279. package/dist/utils/wait.d.ts.map +1 -0
  280. package/dist/utils/wait.js +60 -8
  281. package/dist/utils/wait.js.map +1 -0
  282. package/frontend/build/assets/index.js +7 -5
  283. package/npm-shrinkwrap.json +22 -16
  284. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,3 +1,27 @@
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
+ // Node modules
1
25
  import { createServer } from 'node:http';
2
26
  import https from 'node:https';
3
27
  import os from 'node:os';
@@ -5,14 +29,18 @@ import path from 'node:path';
5
29
  import { existsSync, promises as fs, unlinkSync } from 'node:fs';
6
30
  import EventEmitter from 'node:events';
7
31
  import { appendFile } from 'node:fs/promises';
32
+ // Third-party modules
8
33
  import express from 'express';
9
34
  import WebSocket, { WebSocketServer } from 'ws';
10
35
  import multer from 'multer';
36
+ // AnsiLogger module
11
37
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
38
+ // @matter
12
39
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
13
40
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
14
41
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
15
42
  import { CommissioningOptions } from '@matter/main/types';
43
+ // Matterbridge
16
44
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
17
45
  import { plg } from './matterbridgeTypes.js';
18
46
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
@@ -29,7 +57,7 @@ export class Frontend extends EventEmitter {
29
57
  constructor(matterbridge) {
30
58
  super();
31
59
  this.matterbridge = matterbridge;
32
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
60
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
33
61
  this.log.logNameColor = '\x1b[38;5;97m';
34
62
  }
35
63
  set logLevel(logLevel) {
@@ -38,10 +66,39 @@ export class Frontend extends EventEmitter {
38
66
  async start(port = 8283) {
39
67
  this.port = port;
40
68
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
41
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
69
+ // Initialize multer with the upload directory
70
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
42
71
  const upload = multer({ dest: uploadDir });
72
+ // Create the express app that serves the frontend
43
73
  this.expressApp = express();
74
+ // Inject logging/debug wrapper for route/middleware registration
75
+ /*
76
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
77
+ for (const method of methods) {
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
82
+ try {
83
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
84
+ return original(path, ...rest);
85
+ } catch (err) {
86
+ console.error(`[ERROR] Failed to register route: ${path}`);
87
+ throw err;
88
+ }
89
+ };
90
+ }
91
+ */
92
+ // Log all requests to the server for debugging
93
+ /*
94
+ this.expressApp.use((req, res, next) => {
95
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
96
+ next();
97
+ });
98
+ */
99
+ // Serve static files from '/static' endpoint
44
100
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
101
+ // Read the package.json file to get the frontend version
45
102
  try {
46
103
  this.log.debug(`Reading frontend package.json...`);
47
104
  const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
@@ -49,9 +106,11 @@ export class Frontend extends EventEmitter {
49
106
  this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
50
107
  }
51
108
  catch (error) {
109
+ // istanbul ignore next
52
110
  this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
53
111
  }
54
112
  if (!hasParameter('ssl')) {
113
+ // Create an HTTP server and attach the express app
55
114
  try {
56
115
  this.log.debug(`Creating HTTP server...`);
57
116
  this.httpServer = createServer(this.expressApp);
@@ -61,6 +120,7 @@ export class Frontend extends EventEmitter {
61
120
  this.emit('server_error', error);
62
121
  return;
63
122
  }
123
+ // Listen on the specified port
64
124
  if (hasParameter('ingress')) {
65
125
  this.httpServer.listen(this.port, '0.0.0.0', () => {
66
126
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -101,6 +161,7 @@ export class Frontend extends EventEmitter {
101
161
  let passphrase;
102
162
  let httpsServerOptions = {};
103
163
  if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
164
+ // Load the p12 certificate and the passphrase
104
165
  try {
105
166
  pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
106
167
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -112,7 +173,7 @@ export class Frontend extends EventEmitter {
112
173
  }
113
174
  try {
114
175
  passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
115
- passphrase = passphrase.trim();
176
+ passphrase = passphrase.trim(); // Ensure no extra characters
116
177
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
117
178
  }
118
179
  catch (error) {
@@ -123,6 +184,7 @@ export class Frontend extends EventEmitter {
123
184
  httpsServerOptions = { pfx, passphrase };
124
185
  }
125
186
  else {
187
+ // 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.
126
188
  try {
127
189
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
128
190
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -152,9 +214,10 @@ export class Frontend extends EventEmitter {
152
214
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
153
215
  }
154
216
  if (hasParameter('mtls')) {
155
- httpsServerOptions.requestCert = true;
156
- httpsServerOptions.rejectUnauthorized = true;
217
+ httpsServerOptions.requestCert = true; // Request client certificate
218
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
157
219
  }
220
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
158
221
  try {
159
222
  this.log.debug(`Creating HTTPS server...`);
160
223
  this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
@@ -164,6 +227,7 @@ export class Frontend extends EventEmitter {
164
227
  this.emit('server_error', error);
165
228
  return;
166
229
  }
230
+ // Listen on the specified port
167
231
  if (hasParameter('ingress')) {
168
232
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
169
233
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -195,17 +259,19 @@ export class Frontend extends EventEmitter {
195
259
  return;
196
260
  });
197
261
  }
262
+ // Create a WebSocket server and attach it to the http or https server
198
263
  const wssPort = this.port;
199
264
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
200
265
  this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
201
266
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
202
267
  this.webSocketServer.on('connection', (ws, request) => {
203
268
  const clientIp = request.socket.remoteAddress;
204
- let callbackLogLevel = "notice";
205
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
206
- callbackLogLevel = "info";
207
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
208
- callbackLogLevel = "debug";
269
+ // Set the global logger callback for the WebSocketServer
270
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
271
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
272
+ callbackLogLevel = "info" /* LogLevel.INFO */;
273
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
274
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
209
275
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
210
276
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
211
277
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -227,6 +293,7 @@ export class Frontend extends EventEmitter {
227
293
  }
228
294
  });
229
295
  ws.on('error', (error) => {
296
+ // istanbul ignore next
230
297
  this.log.error(`WebSocket client error: ${error}`);
231
298
  });
232
299
  });
@@ -240,6 +307,7 @@ export class Frontend extends EventEmitter {
240
307
  this.webSocketServer.on('error', (ws, error) => {
241
308
  this.log.error(`WebSocketServer error: ${error}`);
242
309
  });
310
+ // Subscribe to cli events
243
311
  cliEmitter.removeAllListeners();
244
312
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
245
313
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -250,6 +318,8 @@ export class Frontend extends EventEmitter {
250
318
  cliEmitter.on('cpu', (cpuUsage) => {
251
319
  this.wssSendCpuUpdate(cpuUsage);
252
320
  });
321
+ // Endpoint to validate login code
322
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
253
323
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
254
324
  const { password } = req.body;
255
325
  this.log.debug('The frontend sent /api/login', password);
@@ -268,23 +338,27 @@ export class Frontend extends EventEmitter {
268
338
  this.log.warn('/api/login error wrong password');
269
339
  res.json({ valid: false });
270
340
  }
341
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
271
342
  }
272
343
  catch (error) {
273
344
  this.log.error('/api/login error getting password');
274
345
  res.json({ valid: false });
275
346
  }
276
347
  });
348
+ // Endpoint to provide health check for docker
277
349
  this.expressApp.get('/health', (req, res) => {
278
350
  this.log.debug('Express received /health');
279
351
  const healthStatus = {
280
- status: 'ok',
281
- uptime: process.uptime(),
282
- timestamp: new Date().toISOString(),
352
+ status: 'ok', // Indicate service is healthy
353
+ uptime: process.uptime(), // Server uptime in seconds
354
+ timestamp: new Date().toISOString(), // Current timestamp
283
355
  };
284
356
  res.status(200).json(healthStatus);
285
357
  });
358
+ // Endpoint to provide memory usage details
286
359
  this.expressApp.get('/memory', async (req, res) => {
287
360
  this.log.debug('Express received /memory');
361
+ // Memory usage from process
288
362
  const memoryUsageRaw = process.memoryUsage();
289
363
  const memoryUsage = {
290
364
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -293,10 +367,13 @@ export class Frontend extends EventEmitter {
293
367
  external: this.formatMemoryUsage(memoryUsageRaw.external),
294
368
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
295
369
  };
370
+ // V8 heap statistics
296
371
  const { default: v8 } = await import('node:v8');
297
372
  const heapStatsRaw = v8.getHeapStatistics();
298
373
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
374
+ // Format heapStats
299
375
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
376
+ // Format heapSpaces
300
377
  const heapSpaces = heapSpacesRaw.map((space) => ({
301
378
  ...space,
302
379
  space_size: this.formatMemoryUsage(space.space_size),
@@ -314,19 +391,23 @@ export class Frontend extends EventEmitter {
314
391
  };
315
392
  res.status(200).json(memoryReport);
316
393
  });
394
+ // Endpoint to provide settings
317
395
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
318
396
  this.log.debug('The frontend sent /api/settings');
319
397
  res.json(await this.getApiSettings());
320
398
  });
399
+ // Endpoint to provide plugins
321
400
  this.expressApp.get('/api/plugins', async (req, res) => {
322
401
  this.log.debug('The frontend sent /api/plugins');
323
402
  res.json(this.getPlugins());
324
403
  });
404
+ // Endpoint to provide devices
325
405
  this.expressApp.get('/api/devices', async (req, res) => {
326
406
  this.log.debug('The frontend sent /api/devices');
327
407
  const devices = await this.getDevices();
328
408
  res.json(devices);
329
409
  });
410
+ // Endpoint to view the matterbridge log
330
411
  this.expressApp.get('/api/view-mblog', async (req, res) => {
331
412
  this.log.debug('The frontend sent /api/view-mblog');
332
413
  try {
@@ -339,6 +420,7 @@ export class Frontend extends EventEmitter {
339
420
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
340
421
  }
341
422
  });
423
+ // Endpoint to view the matter.js log
342
424
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
343
425
  this.log.debug('The frontend sent /api/view-mjlog');
344
426
  try {
@@ -351,9 +433,11 @@ export class Frontend extends EventEmitter {
351
433
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
352
434
  }
353
435
  });
436
+ // Endpoint to view the diagnostic.log
354
437
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
355
438
  this.log.debug('The frontend sent /api/view-diagnostic');
356
439
  const serverNodes = [];
440
+ // istanbul ignore else
357
441
  if (this.matterbridge.bridgeMode === 'bridge') {
358
442
  if (this.matterbridge.serverNode)
359
443
  serverNodes.push(this.matterbridge.serverNode);
@@ -364,6 +448,7 @@ export class Frontend extends EventEmitter {
364
448
  serverNodes.push(plugin.serverNode);
365
449
  }
366
450
  }
451
+ // istanbul ignore next
367
452
  for (const device of this.matterbridge.getDevices()) {
368
453
  if (device.serverNode)
369
454
  serverNodes.push(device.serverNode);
@@ -386,17 +471,20 @@ export class Frontend extends EventEmitter {
386
471
  values: [...serverNodes],
387
472
  })));
388
473
  delete Logger.destinations.diagnostic;
389
- await wait(500);
474
+ await wait(500); // Wait for the log to be written
390
475
  try {
391
476
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
392
477
  res.type('text/plain');
393
478
  res.send(data.slice(29));
394
479
  }
395
480
  catch (error) {
481
+ // istanbul ignore next
396
482
  this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
483
+ // istanbul ignore next
397
484
  res.status(500).send('Error reading diagnostic log file.');
398
485
  }
399
486
  });
487
+ // Endpoint to view the shelly log
400
488
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
401
489
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
402
490
  try {
@@ -409,6 +497,7 @@ export class Frontend extends EventEmitter {
409
497
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
410
498
  }
411
499
  });
500
+ // Endpoint to download the matterbridge log
412
501
  this.expressApp.get('/api/download-mblog', async (req, res) => {
413
502
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
414
503
  try {
@@ -422,12 +511,14 @@ export class Frontend extends EventEmitter {
422
511
  }
423
512
  res.type('text/plain');
424
513
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
514
+ /* istanbul ignore if */
425
515
  if (error) {
426
516
  this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
427
517
  res.status(500).send('Error downloading the matterbridge log file');
428
518
  }
429
519
  });
430
520
  });
521
+ // Endpoint to download the matter log
431
522
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
432
523
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
433
524
  try {
@@ -441,12 +532,14 @@ export class Frontend extends EventEmitter {
441
532
  }
442
533
  res.type('text/plain');
443
534
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
535
+ /* istanbul ignore if */
444
536
  if (error) {
445
537
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
446
538
  res.status(500).send('Error downloading the matter log file');
447
539
  }
448
540
  });
449
541
  });
542
+ // Endpoint to download the shelly log
450
543
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
451
544
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
452
545
  try {
@@ -460,74 +553,90 @@ export class Frontend extends EventEmitter {
460
553
  }
461
554
  res.type('text/plain');
462
555
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
556
+ /* istanbul ignore if */
463
557
  if (error) {
464
558
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
465
559
  res.status(500).send('Error downloading Shelly system log file');
466
560
  }
467
561
  });
468
562
  });
563
+ // Endpoint to download the matterbridge storage directory
469
564
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
470
565
  this.log.debug('The frontend sent /api/download-mbstorage');
471
566
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
472
567
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
568
+ /* istanbul ignore if */
473
569
  if (error) {
474
570
  this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
475
571
  res.status(500).send('Error downloading the matterbridge storage file');
476
572
  }
477
573
  });
478
574
  });
575
+ // Endpoint to download the matter storage file
479
576
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
480
577
  this.log.debug('The frontend sent /api/download-mjstorage');
481
578
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
482
579
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
580
+ /* istanbul ignore if */
483
581
  if (error) {
484
582
  this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
485
583
  res.status(500).send('Error downloading the matter storage zip file');
486
584
  }
487
585
  });
488
586
  });
587
+ // Endpoint to download the matterbridge plugin directory
489
588
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
490
589
  this.log.debug('The frontend sent /api/download-pluginstorage');
491
590
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
492
591
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
592
+ /* istanbul ignore if */
493
593
  if (error) {
494
594
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
495
595
  res.status(500).send('Error downloading the matterbridge plugin storage file');
496
596
  }
497
597
  });
498
598
  });
599
+ // Endpoint to download the matterbridge plugin config files
499
600
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
500
601
  this.log.debug('The frontend sent /api/download-pluginconfig');
501
602
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
502
603
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
604
+ /* istanbul ignore if */
503
605
  if (error) {
504
606
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
505
607
  res.status(500).send('Error downloading the matterbridge plugin config file');
506
608
  }
507
609
  });
508
610
  });
611
+ // Endpoint to download the matterbridge backup (created with the backup command)
509
612
  this.expressApp.get('/api/download-backup', async (req, res) => {
510
613
  this.log.debug('The frontend sent /api/download-backup');
511
614
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
615
+ /* istanbul ignore if */
512
616
  if (error) {
513
617
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
514
618
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
515
619
  }
516
620
  });
517
621
  });
622
+ // Endpoint to upload a package
518
623
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
519
624
  const { filename } = req.body;
520
625
  const file = req.file;
626
+ /* istanbul ignore if */
521
627
  if (!file || !filename) {
522
628
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
523
629
  res.status(400).send('Invalid request: file and filename are required');
524
630
  return;
525
631
  }
526
632
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
633
+ // Define the path where the plugin file will be saved
527
634
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
528
635
  try {
636
+ // Move the uploaded file to the specified path
529
637
  await fs.rename(file.path, filePath);
530
638
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
639
+ // Install the plugin package
531
640
  if (filename.endsWith('.tgz')) {
532
641
  const { spawnCommand } = await import('./utils/spawn.js');
533
642
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
@@ -547,6 +656,7 @@ export class Frontend extends EventEmitter {
547
656
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
548
657
  }
549
658
  });
659
+ // Fallback for routing (must be the last route)
550
660
  this.expressApp.use((req, res) => {
551
661
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
552
662
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -555,13 +665,16 @@ export class Frontend extends EventEmitter {
555
665
  }
556
666
  async stop() {
557
667
  this.log.debug('Stopping the frontend...');
668
+ // Remove listeners from the express app
558
669
  if (this.expressApp) {
559
670
  this.expressApp.removeAllListeners();
560
671
  this.expressApp = undefined;
561
672
  this.log.debug('Frontend app closed successfully');
562
673
  }
674
+ // Close the WebSocket server
563
675
  if (this.webSocketServer) {
564
676
  this.log.debug('Closing WebSocket server...');
677
+ // Close all active connections
565
678
  this.webSocketServer.clients.forEach((client) => {
566
679
  if (client.readyState === WebSocket.OPEN) {
567
680
  client.close();
@@ -570,6 +683,7 @@ export class Frontend extends EventEmitter {
570
683
  await withTimeout(new Promise((resolve) => {
571
684
  this.webSocketServer?.close((error) => {
572
685
  if (error) {
686
+ // istanbul ignore next
573
687
  this.log.error(`Error closing WebSocket server: ${error}`);
574
688
  }
575
689
  else {
@@ -582,8 +696,27 @@ export class Frontend extends EventEmitter {
582
696
  this.webSocketServer.removeAllListeners();
583
697
  this.webSocketServer = undefined;
584
698
  }
699
+ // Close the http server
585
700
  if (this.httpServer) {
586
701
  this.log.debug('Closing http server...');
702
+ /*
703
+ await withTimeout(
704
+ new Promise<void>((resolve) => {
705
+ this.httpServer?.close((error) => {
706
+ if (error) {
707
+ // istanbul ignore next
708
+ this.log.error(`Error closing http server: ${error}`);
709
+ } else {
710
+ this.log.debug('Http server closed successfully');
711
+ this.emit('server_stopped');
712
+ }
713
+ resolve();
714
+ });
715
+ }),
716
+ 5000,
717
+ false,
718
+ );
719
+ */
587
720
  this.httpServer.close();
588
721
  this.log.debug('Http server closed successfully');
589
722
  this.listening = false;
@@ -592,8 +725,27 @@ export class Frontend extends EventEmitter {
592
725
  this.httpServer = undefined;
593
726
  this.log.debug('Frontend http server closed successfully');
594
727
  }
728
+ // Close the https server
595
729
  if (this.httpsServer) {
596
730
  this.log.debug('Closing https server...');
731
+ /*
732
+ await withTimeout(
733
+ new Promise<void>((resolve) => {
734
+ this.httpsServer?.close((error) => {
735
+ if (error) {
736
+ // istanbul ignore next
737
+ this.log.error(`Error closing https server: ${error}`);
738
+ } else {
739
+ this.log.debug('Https server closed successfully');
740
+ this.emit('server_stopped');
741
+ }
742
+ resolve();
743
+ });
744
+ }),
745
+ 5000,
746
+ false,
747
+ );
748
+ */
597
749
  this.httpsServer.close();
598
750
  this.log.debug('Https server closed successfully');
599
751
  this.listening = false;
@@ -604,6 +756,7 @@ export class Frontend extends EventEmitter {
604
756
  }
605
757
  this.log.debug('Frontend stopped successfully');
606
758
  }
759
+ // Function to format bytes to KB, MB, or GB
607
760
  formatMemoryUsage = (bytes) => {
608
761
  if (bytes >= 1024 ** 3) {
609
762
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -615,6 +768,7 @@ export class Frontend extends EventEmitter {
615
768
  return `${(bytes / 1024).toFixed(2)} KB`;
616
769
  }
617
770
  };
771
+ // Function to format system uptime with only the most significant unit
618
772
  formatOsUpTime = (seconds) => {
619
773
  if (seconds >= 86400) {
620
774
  const days = Math.floor(seconds / 86400);
@@ -630,7 +784,13 @@ export class Frontend extends EventEmitter {
630
784
  }
631
785
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
632
786
  };
787
+ /**
788
+ * Retrieves the api settings data.
789
+ *
790
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
791
+ */
633
792
  async getApiSettings() {
793
+ // Update the system information
634
794
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
635
795
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
636
796
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -639,6 +799,7 @@ export class Frontend extends EventEmitter {
639
799
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
640
800
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
641
801
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
802
+ // Update the matterbridge information
642
803
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
643
804
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
644
805
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
@@ -652,6 +813,12 @@ export class Frontend extends EventEmitter {
652
813
  this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
653
814
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
654
815
  }
816
+ /**
817
+ * Retrieves the reachable attribute.
818
+ *
819
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
820
+ * @returns {boolean} The reachable attribute.
821
+ */
655
822
  getReachability(device) {
656
823
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
657
824
  return false;
@@ -663,6 +830,12 @@ export class Frontend extends EventEmitter {
663
830
  return true;
664
831
  return false;
665
832
  }
833
+ /**
834
+ * Retrieves the power source attribute.
835
+ *
836
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
837
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
838
+ */
666
839
  getPowerSource(endpoint) {
667
840
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
668
841
  return undefined;
@@ -678,13 +851,22 @@ export class Frontend extends EventEmitter {
678
851
  }
679
852
  return;
680
853
  };
854
+ // Root endpoint
681
855
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
682
856
  return powerSource(endpoint);
857
+ // Child endpoints
683
858
  for (const child of endpoint.getChildEndpoints()) {
684
859
  if (child.hasClusterServer(PowerSource.Cluster.id))
685
860
  return powerSource(child);
686
861
  }
687
862
  }
863
+ /**
864
+ * Retrieves the cluster text description from a given device.
865
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
866
+ *
867
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
868
+ * @returns {string} The attributes description of the cluster servers in the device.
869
+ */
688
870
  getClusterTextFromDevice(device) {
689
871
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
690
872
  return '';
@@ -695,6 +877,7 @@ export class Frontend extends EventEmitter {
695
877
  if (composed)
696
878
  return 'Composed: ' + composed.value;
697
879
  }
880
+ // istanbul ignore next cause is not reachable
698
881
  return '';
699
882
  };
700
883
  const getFixedLabel = (device) => {
@@ -704,11 +887,13 @@ export class Frontend extends EventEmitter {
704
887
  if (composed)
705
888
  return 'Composed: ' + composed.value;
706
889
  }
890
+ // istanbul ignore next cause is not reacheable
707
891
  return '';
708
892
  };
709
893
  let attributes = '';
710
894
  let supportedModes = [];
711
895
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
896
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
712
897
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
713
898
  return;
714
899
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -798,11 +983,17 @@ export class Frontend extends EventEmitter {
798
983
  if (clusterName === 'userLabel' && attributeName === 'labelList')
799
984
  attributes += `${getUserLabel(device)} `;
800
985
  });
986
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
801
987
  return attributes.trimStart().trimEnd();
802
988
  }
989
+ /**
990
+ * Retrieves the registered plugins sanitized for res.json().
991
+ *
992
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
993
+ */
803
994
  getPlugins() {
804
995
  if (this.matterbridge.hasCleanupStarted)
805
- return [];
996
+ return []; // Skip if cleanup has started
806
997
  const baseRegisteredPlugins = [];
807
998
  for (const plugin of this.matterbridge.plugins) {
808
999
  baseRegisteredPlugins.push({
@@ -830,18 +1021,27 @@ export class Frontend extends EventEmitter {
830
1021
  schemaJson: plugin.schemaJson,
831
1022
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
832
1023
  hasBlackList: plugin.configJson?.blackList !== undefined,
1024
+ // Childbridge mode specific data
833
1025
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
834
1026
  });
835
1027
  }
836
1028
  return baseRegisteredPlugins;
837
1029
  }
1030
+ /**
1031
+ * Retrieves the devices from Matterbridge.
1032
+ *
1033
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1034
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1035
+ */
838
1036
  async getDevices(pluginName) {
839
1037
  if (this.matterbridge.hasCleanupStarted)
840
- return [];
1038
+ return []; // Skip if cleanup has started
841
1039
  const devices = [];
842
1040
  for (const device of this.matterbridge.devices.array()) {
1041
+ // Filter by pluginName if provided
843
1042
  if (pluginName && pluginName !== device.plugin)
844
1043
  continue;
1044
+ // Check if the device has the required properties
845
1045
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
846
1046
  continue;
847
1047
  devices.push({
@@ -861,22 +1061,37 @@ export class Frontend extends EventEmitter {
861
1061
  }
862
1062
  return devices;
863
1063
  }
1064
+ /**
1065
+ * Retrieves the clusters from a given plugin and endpoint number.
1066
+ *
1067
+ * Response for /api/clusters
1068
+ *
1069
+ * @param {string} pluginName - The name of the plugin.
1070
+ * @param {number} endpointNumber - The endpoint number.
1071
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1072
+ */
864
1073
  getClusters(pluginName, endpointNumber) {
865
1074
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
866
1075
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
867
1076
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
868
1077
  return;
869
1078
  }
1079
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1080
+ // Get the device types from the main endpoint
870
1081
  const deviceTypes = [];
871
1082
  const clusters = [];
872
1083
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
873
1084
  deviceTypes.push(d.deviceType);
874
1085
  });
1086
+ // Get the clusters from the main endpoint
875
1087
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
876
1088
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
877
1089
  return;
878
1090
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
879
1091
  return;
1092
+ // console.log(
1093
+ // `${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}`,
1094
+ // );
880
1095
  clusters.push({
881
1096
  endpoint: endpoint.number.toString(),
882
1097
  number: endpoint.number,
@@ -890,12 +1105,19 @@ export class Frontend extends EventEmitter {
890
1105
  attributeLocalValue: attributeValue,
891
1106
  });
892
1107
  });
1108
+ // Get the child endpoints
893
1109
  const childEndpoints = endpoint.getChildEndpoints();
1110
+ // if (childEndpoints.length === 0) {
1111
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1112
+ // }
894
1113
  childEndpoints.forEach((childEndpoint) => {
1114
+ // istanbul ignore if cause is not reachable: should never happen but ...
895
1115
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
896
1116
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
897
1117
  return;
898
1118
  }
1119
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1120
+ // Get the device types of the child endpoint
899
1121
  const deviceTypes = [];
900
1122
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
901
1123
  deviceTypes.push(d.deviceType);
@@ -905,6 +1127,9 @@ export class Frontend extends EventEmitter {
905
1127
  return;
906
1128
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
907
1129
  return;
1130
+ // console.log(
1131
+ // `${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}`,
1132
+ // );
908
1133
  clusters.push({
909
1134
  endpoint: childEndpoint.number.toString(),
910
1135
  number: childEndpoint.number,
@@ -921,6 +1146,13 @@ export class Frontend extends EventEmitter {
921
1146
  });
922
1147
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
923
1148
  }
1149
+ /**
1150
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1151
+ *
1152
+ * @param {WebSocket} client - The websocket client that sent the message.
1153
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1154
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1155
+ */
924
1156
  async wsMessageHandler(client, message) {
925
1157
  let data;
926
1158
  const sendResponse = (data) => {
@@ -940,7 +1172,7 @@ export class Frontend extends EventEmitter {
940
1172
  };
941
1173
  try {
942
1174
  data = JSON.parse(message.toString());
943
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1175
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
944
1176
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
945
1177
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
946
1178
  return;
@@ -983,35 +1215,48 @@ export class Frontend extends EventEmitter {
983
1215
  this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
984
1216
  const packageName = localData.params.packageName.replace(/@.*$/, '');
985
1217
  if (localData.params.restart === false && packageName !== 'matterbridge') {
1218
+ // The install comes from InstallPlugins
986
1219
  this.matterbridge.plugins
987
1220
  .add(packageName)
988
1221
  .then((plugin) => {
1222
+ // istanbul ignore next if
989
1223
  if (plugin) {
1224
+ // The plugin is not registered
990
1225
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1226
+ // In childbridge mode the plugins server node is not started when added
991
1227
  if (this.matterbridge.bridgeMode === 'childbridge')
992
1228
  this.wssSendRestartRequired(true, true);
993
1229
  this.matterbridge.plugins
994
1230
  .load(plugin, true, 'The plugin has been added', true)
1231
+ // eslint-disable-next-line promise/no-nesting
995
1232
  .then(() => {
996
1233
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
997
1234
  this.wssSendRefreshRequired('plugins');
998
1235
  return;
999
1236
  })
1237
+ // eslint-disable-next-line promise/no-nesting
1000
1238
  .catch((_error) => {
1239
+ //
1001
1240
  });
1002
1241
  }
1003
1242
  else {
1243
+ // The plugin is already registered
1004
1244
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1005
1245
  this.wssSendRefreshRequired('plugins');
1006
1246
  this.wssSendRestartRequired(true, true);
1007
1247
  }
1008
1248
  return;
1009
1249
  })
1250
+ // eslint-disable-next-line promise/no-nesting
1010
1251
  .catch((_error) => {
1252
+ //
1011
1253
  });
1012
1254
  }
1013
1255
  else {
1256
+ // The package is matterbridge
1257
+ // istanbul ignore next
1014
1258
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1259
+ // istanbul ignore next if
1015
1260
  if (this.matterbridge.restartMode !== '') {
1016
1261
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1017
1262
  this.matterbridge.shutdownProcess();
@@ -1034,7 +1279,9 @@ export class Frontend extends EventEmitter {
1034
1279
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
1035
1280
  return;
1036
1281
  }
1282
+ // The package is a plugin
1037
1283
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1284
+ // istanbul ignore next if
1038
1285
  if (plugin) {
1039
1286
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1040
1287
  await this.matterbridge.plugins.remove(data.params.packageName);
@@ -1042,6 +1289,7 @@ export class Frontend extends EventEmitter {
1042
1289
  this.wssSendRefreshRequired('plugins');
1043
1290
  this.wssSendRefreshRequired('devices');
1044
1291
  }
1292
+ // Uninstall the package
1045
1293
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1046
1294
  const { spawnCommand } = await import('./utils/spawn.js');
1047
1295
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
@@ -1084,6 +1332,7 @@ export class Frontend extends EventEmitter {
1084
1332
  return;
1085
1333
  })
1086
1334
  .catch((_error) => {
1335
+ //
1087
1336
  });
1088
1337
  }
1089
1338
  else {
@@ -1131,6 +1380,7 @@ export class Frontend extends EventEmitter {
1131
1380
  return;
1132
1381
  })
1133
1382
  .catch((_error) => {
1383
+ //
1134
1384
  });
1135
1385
  }
1136
1386
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1156,6 +1406,7 @@ export class Frontend extends EventEmitter {
1156
1406
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1157
1407
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1158
1408
  if (plugin.serverNode) {
1409
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1159
1410
  await this.matterbridge.stopServerNode(plugin.serverNode);
1160
1411
  plugin.serverNode = undefined;
1161
1412
  }
@@ -1165,18 +1416,20 @@ export class Frontend extends EventEmitter {
1165
1416
  this.matterbridge.devices.remove(device);
1166
1417
  }
1167
1418
  }
1419
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1168
1420
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1169
1421
  await this.matterbridge.createDynamicPlugin(plugin);
1170
1422
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1171
- plugin.restartRequired = false;
1423
+ plugin.restartRequired = false; // Reset plugin restartRequired
1172
1424
  let needRestart = 0;
1173
1425
  for (const plugin of this.matterbridge.plugins) {
1174
1426
  if (plugin.restartRequired)
1175
1427
  needRestart++;
1176
1428
  }
1177
1429
  if (needRestart === 0) {
1178
- this.wssSendRestartNotRequired(true);
1430
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1179
1431
  }
1432
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1180
1433
  if (plugin.serverNode)
1181
1434
  await this.matterbridge.startServerNode(plugin.serverNode);
1182
1435
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1432,22 +1685,22 @@ export class Frontend extends EventEmitter {
1432
1685
  if (isValidString(data.params.value, 4)) {
1433
1686
  this.log.debug('Matterbridge logger level:', data.params.value);
1434
1687
  if (data.params.value === 'Debug') {
1435
- await this.matterbridge.setLogLevel("debug");
1688
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1436
1689
  }
1437
1690
  else if (data.params.value === 'Info') {
1438
- await this.matterbridge.setLogLevel("info");
1691
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1439
1692
  }
1440
1693
  else if (data.params.value === 'Notice') {
1441
- await this.matterbridge.setLogLevel("notice");
1694
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1442
1695
  }
1443
1696
  else if (data.params.value === 'Warn') {
1444
- await this.matterbridge.setLogLevel("warn");
1697
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1445
1698
  }
1446
1699
  else if (data.params.value === 'Error') {
1447
- await this.matterbridge.setLogLevel("error");
1700
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1448
1701
  }
1449
1702
  else if (data.params.value === 'Fatal') {
1450
- await this.matterbridge.setLogLevel("fatal");
1703
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1451
1704
  }
1452
1705
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1453
1706
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1458,6 +1711,7 @@ export class Frontend extends EventEmitter {
1458
1711
  this.log.debug('Matterbridge file log:', data.params.value);
1459
1712
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1460
1713
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1714
+ // Create the file logger for matterbridge
1461
1715
  if (data.params.value)
1462
1716
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1463
1717
  else
@@ -1536,6 +1790,7 @@ export class Frontend extends EventEmitter {
1536
1790
  }
1537
1791
  break;
1538
1792
  case 'setmatterport':
1793
+ // eslint-disable-next-line no-case-declarations
1539
1794
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1540
1795
  if (isValidNumber(port, 5540, 5600)) {
1541
1796
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1553,6 +1808,7 @@ export class Frontend extends EventEmitter {
1553
1808
  }
1554
1809
  break;
1555
1810
  case 'setmatterdiscriminator':
1811
+ // eslint-disable-next-line no-case-declarations
1556
1812
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1557
1813
  if (isValidNumber(discriminator, 0, 4095)) {
1558
1814
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1570,6 +1826,7 @@ export class Frontend extends EventEmitter {
1570
1826
  }
1571
1827
  break;
1572
1828
  case 'setmatterpasscode':
1829
+ // eslint-disable-next-line no-case-declarations
1573
1830
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1574
1831
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1575
1832
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -1613,15 +1870,19 @@ export class Frontend extends EventEmitter {
1613
1870
  return;
1614
1871
  }
1615
1872
  const config = plugin.configJson;
1873
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1616
1874
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1875
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1617
1876
  if (select === 'serial')
1618
1877
  this.log.info(`Selected device serial ${data.params.serial}`);
1619
1878
  if (select === 'name')
1620
1879
  this.log.info(`Selected device name ${data.params.name}`);
1621
1880
  if (config && select && (select === 'serial' || select === 'name')) {
1881
+ // Remove postfix from the serial if it exists
1622
1882
  if (config.postfix) {
1623
1883
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1624
1884
  }
1885
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1625
1886
  if (isValidArray(config.whiteList, 1)) {
1626
1887
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1627
1888
  config.whiteList.push(data.params.serial);
@@ -1630,6 +1891,7 @@ export class Frontend extends EventEmitter {
1630
1891
  config.whiteList.push(data.params.name);
1631
1892
  }
1632
1893
  }
1894
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1633
1895
  if (isValidArray(config.blackList, 1)) {
1634
1896
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1635
1897
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1657,7 +1919,9 @@ export class Frontend extends EventEmitter {
1657
1919
  return;
1658
1920
  }
1659
1921
  const config = plugin.configJson;
1922
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1660
1923
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1924
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1661
1925
  if (select === 'serial')
1662
1926
  this.log.info(`Unselected device serial ${data.params.serial}`);
1663
1927
  if (select === 'name')
@@ -1666,6 +1930,7 @@ export class Frontend extends EventEmitter {
1666
1930
  if (config.postfix) {
1667
1931
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1668
1932
  }
1933
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1669
1934
  if (isValidArray(config.whiteList, 1)) {
1670
1935
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1671
1936
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1674,6 +1939,7 @@ export class Frontend extends EventEmitter {
1674
1939
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1675
1940
  }
1676
1941
  }
1942
+ // Add the serial to the blackList
1677
1943
  if (isValidArray(config.blackList)) {
1678
1944
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1679
1945
  config.blackList.push(data.params.serial);
@@ -1696,6 +1962,7 @@ export class Frontend extends EventEmitter {
1696
1962
  }
1697
1963
  }
1698
1964
  else {
1965
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1699
1966
  const localData = data;
1700
1967
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1701
1968
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1705,80 +1972,206 @@ export class Frontend extends EventEmitter {
1705
1972
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1706
1973
  }
1707
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
+ */
1708
1988
  wssSendLogMessage(level, time, name, message) {
1709
1989
  if (!level || !time || !name || !message)
1710
1990
  return;
1991
+ // Remove ANSI escape codes from the message
1992
+ // eslint-disable-next-line no-control-regex
1711
1993
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1994
+ // Remove leading asterisks from the message
1712
1995
  message = message.replace(/^\*+/, '');
1996
+ // Replace all occurrences of \t and \n
1713
1997
  message = message.replace(/[\t\n]/g, '');
1998
+ // Remove non-printable characters
1999
+ // eslint-disable-next-line no-control-regex
1714
2000
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2001
+ // Replace all occurrences of \" with "
1715
2002
  message = message.replace(/\\"/g, '"');
2003
+ // Define the maximum allowed length for continuous characters without a space
1716
2004
  const maxContinuousLength = 100;
1717
2005
  const keepStartLength = 20;
1718
2006
  const keepEndLength = 20;
1719
- message = message
1720
- .split(' ')
1721
- .map((word) => {
1722
- if (word.length > maxContinuousLength) {
1723
- return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1724
- }
1725
- return word;
1726
- })
1727
- .join(' ');
2007
+ // Split the message into words
2008
+ if (level !== 'spawn') {
2009
+ message = message
2010
+ .split(' ')
2011
+ .map((word) => {
2012
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2013
+ if (word.length > maxContinuousLength) {
2014
+ return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2015
+ }
2016
+ return word;
2017
+ })
2018
+ .join(' ');
2019
+ }
2020
+ // Send the message to all connected clients
1728
2021
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
1729
2022
  }
2023
+ /**
2024
+ * Sends a need to refresh WebSocket message to all connected clients.
2025
+ *
2026
+ * @param {string} changed - The changed value.
2027
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2028
+ * possible values for changed:
2029
+ * - 'settings'
2030
+ * - 'plugins'
2031
+ * - 'devices'
2032
+ * - 'matter' with param 'matter' (QRDiv component)
2033
+ * @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
2034
+ */
1730
2035
  wssSendRefreshRequired(changed, params) {
1731
2036
  this.log.debug('Sending a refresh required message to all connected clients');
2037
+ // Send the message to all connected clients
1732
2038
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
1733
2039
  }
2040
+ /**
2041
+ * Sends a need to restart WebSocket message to all connected clients.
2042
+ *
2043
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2044
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2045
+ */
1734
2046
  wssSendRestartRequired(snackbar = true, fixed = false) {
1735
2047
  this.log.debug('Sending a restart required message to all connected clients');
1736
2048
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1737
2049
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1738
2050
  if (snackbar === true)
1739
2051
  this.wssSendSnackbarMessage(`Restart required`, 0);
2052
+ // Send the message to all connected clients
1740
2053
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1741
2054
  }
2055
+ /**
2056
+ * Sends a no need to restart WebSocket message to all connected clients.
2057
+ *
2058
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2059
+ */
1742
2060
  wssSendRestartNotRequired(snackbar = true) {
1743
2061
  this.log.debug('Sending a restart not required message to all connected clients');
1744
2062
  this.matterbridge.matterbridgeInformation.restartRequired = false;
1745
2063
  if (snackbar === true)
1746
2064
  this.wssSendCloseSnackbarMessage(`Restart required`);
2065
+ // Send the message to all connected clients
1747
2066
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1748
2067
  }
2068
+ /**
2069
+ * Sends a need to update WebSocket message to all connected clients.
2070
+ *
2071
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2072
+ */
1749
2073
  wssSendUpdateRequired(devVersion = false) {
1750
2074
  this.log.debug('Sending an update required message to all connected clients');
1751
2075
  this.matterbridge.matterbridgeInformation.updateRequired = true;
2076
+ // Send the message to all connected clients
1752
2077
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1753
2078
  }
2079
+ /**
2080
+ * Sends a cpu update message to all connected clients.
2081
+ *
2082
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2083
+ */
1754
2084
  wssSendCpuUpdate(cpuUsage) {
1755
2085
  if (hasParameter('debug'))
1756
2086
  this.log.debug('Sending a cpu update message to all connected clients');
2087
+ // Send the message to all connected clients
1757
2088
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
1758
2089
  }
2090
+ /**
2091
+ * Sends a memory update message to all connected clients.
2092
+ *
2093
+ * @param {string} totalMemory - The total memory in bytes.
2094
+ * @param {string} freeMemory - The free memory in bytes.
2095
+ * @param {string} rss - The resident set size in bytes.
2096
+ * @param {string} heapTotal - The total heap memory in bytes.
2097
+ * @param {string} heapUsed - The used heap memory in bytes.
2098
+ * @param {string} external - The external memory in bytes.
2099
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2100
+ */
1759
2101
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1760
2102
  if (hasParameter('debug'))
1761
2103
  this.log.debug('Sending a memory update message to all connected clients');
2104
+ // Send the message to all connected clients
1762
2105
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1763
2106
  }
2107
+ /**
2108
+ * Sends an uptime update message to all connected clients.
2109
+ *
2110
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2111
+ * @param {string} processUptime - The process uptime in a human-readable format.
2112
+ */
1764
2113
  wssSendUptimeUpdate(systemUptime, processUptime) {
1765
2114
  if (hasParameter('debug'))
1766
2115
  this.log.debug('Sending a uptime update message to all connected clients');
2116
+ // Send the message to all connected clients
1767
2117
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
1768
2118
  }
2119
+ /**
2120
+ * Sends an open snackbar message to all connected clients.
2121
+ *
2122
+ * @param {string} message - The message to send.
2123
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2124
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2125
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2126
+ *
2127
+ * @remarks
2128
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2129
+ */
1769
2130
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1770
2131
  this.log.debug('Sending a snackbar message to all connected clients');
2132
+ // Send the message to all connected clients
1771
2133
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
1772
2134
  }
2135
+ /**
2136
+ * Sends a close snackbar message to all connected clients.
2137
+ * It will close the snackbar message with the same message and timeout = 0.
2138
+ *
2139
+ * @param {string} message - The message to send.
2140
+ */
1773
2141
  wssSendCloseSnackbarMessage(message) {
1774
2142
  this.log.debug('Sending a close snackbar message to all connected clients');
2143
+ // Send the message to all connected clients
1775
2144
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1776
2145
  }
2146
+ /**
2147
+ * Sends an attribute update message to all connected WebSocket clients.
2148
+ *
2149
+ * @param {string | undefined} plugin - The name of the plugin.
2150
+ * @param {string | undefined} serialNumber - The serial number of the device.
2151
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2152
+ * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2153
+ * @param {string} id - The endpoint id where the attribute belongs.
2154
+ * @param {string} cluster - The cluster name where the attribute belongs.
2155
+ * @param {string} attribute - The name of the attribute that changed.
2156
+ * @param {number | string | boolean} value - The new value of the attribute.
2157
+ *
2158
+ * @remarks
2159
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2160
+ * with the updated attribute information.
2161
+ */
1777
2162
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1778
2163
  this.log.debug('Sending an attribute update message to all connected clients');
2164
+ // Send the message to all connected clients
1779
2165
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1780
2166
  }
2167
+ /**
2168
+ * Sends a message to all connected clients.
2169
+ * This is an helper function to send a broadcast message to all connected clients.
2170
+ *
2171
+ * @param {WsMessageBroadcast} msg - The message to send.
2172
+ */
1781
2173
  wssBroadcastMessage(msg) {
2174
+ // Send the message to all connected clients
1782
2175
  const stringifiedMsg = JSON.stringify(msg);
1783
2176
  if (msg.method !== 'log')
1784
2177
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -1789,3 +2182,4 @@ export class Frontend extends EventEmitter {
1789
2182
  });
1790
2183
  }
1791
2184
  }
2185
+ //# sourceMappingURL=frontend.js.map