matterbridge 3.2.8-dev-20250920-1a6178d → 3.2.8

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/dist/cli.d.ts +26 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +91 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cliEmitter.d.ts +34 -0
  6. package/dist/cliEmitter.d.ts.map +1 -0
  7. package/dist/cliEmitter.js +30 -0
  8. package/dist/cliEmitter.js.map +1 -0
  9. package/dist/clusters/export.d.ts +2 -0
  10. package/dist/clusters/export.d.ts.map +1 -0
  11. package/dist/clusters/export.js +2 -0
  12. package/dist/clusters/export.js.map +1 -0
  13. package/dist/defaultConfigSchema.d.ts +28 -0
  14. package/dist/defaultConfigSchema.d.ts.map +1 -0
  15. package/dist/defaultConfigSchema.js +24 -0
  16. package/dist/defaultConfigSchema.js.map +1 -0
  17. package/dist/deviceManager.d.ts +112 -0
  18. package/dist/deviceManager.d.ts.map +1 -0
  19. package/dist/deviceManager.js +94 -1
  20. package/dist/deviceManager.js.map +1 -0
  21. package/dist/devices/airConditioner.d.ts +98 -0
  22. package/dist/devices/airConditioner.d.ts.map +1 -0
  23. package/dist/devices/airConditioner.js +57 -0
  24. package/dist/devices/airConditioner.js.map +1 -0
  25. package/dist/devices/batteryStorage.d.ts +48 -0
  26. package/dist/devices/batteryStorage.d.ts.map +1 -0
  27. package/dist/devices/batteryStorage.js +48 -1
  28. package/dist/devices/batteryStorage.js.map +1 -0
  29. package/dist/devices/cooktop.d.ts +60 -0
  30. package/dist/devices/cooktop.d.ts.map +1 -0
  31. package/dist/devices/cooktop.js +55 -0
  32. package/dist/devices/cooktop.js.map +1 -0
  33. package/dist/devices/dishwasher.d.ts +71 -0
  34. package/dist/devices/dishwasher.d.ts.map +1 -0
  35. package/dist/devices/dishwasher.js +57 -0
  36. package/dist/devices/dishwasher.js.map +1 -0
  37. package/dist/devices/evse.d.ts +75 -0
  38. package/dist/devices/evse.d.ts.map +1 -0
  39. package/dist/devices/evse.js +74 -10
  40. package/dist/devices/evse.js.map +1 -0
  41. package/dist/devices/export.d.ts +17 -0
  42. package/dist/devices/export.d.ts.map +1 -0
  43. package/dist/devices/export.js +5 -0
  44. package/dist/devices/export.js.map +1 -0
  45. package/dist/devices/extractorHood.d.ts +46 -0
  46. package/dist/devices/extractorHood.d.ts.map +1 -0
  47. package/dist/devices/extractorHood.js +42 -0
  48. package/dist/devices/extractorHood.js.map +1 -0
  49. package/dist/devices/heatPump.d.ts +47 -0
  50. package/dist/devices/heatPump.d.ts.map +1 -0
  51. package/dist/devices/heatPump.js +50 -2
  52. package/dist/devices/heatPump.js.map +1 -0
  53. package/dist/devices/laundryDryer.d.ts +67 -0
  54. package/dist/devices/laundryDryer.d.ts.map +1 -0
  55. package/dist/devices/laundryDryer.js +62 -3
  56. package/dist/devices/laundryDryer.js.map +1 -0
  57. package/dist/devices/laundryWasher.d.ts +81 -0
  58. package/dist/devices/laundryWasher.d.ts.map +1 -0
  59. package/dist/devices/laundryWasher.js +70 -4
  60. package/dist/devices/laundryWasher.js.map +1 -0
  61. package/dist/devices/microwaveOven.d.ts +168 -0
  62. package/dist/devices/microwaveOven.d.ts.map +1 -0
  63. package/dist/devices/microwaveOven.js +88 -5
  64. package/dist/devices/microwaveOven.js.map +1 -0
  65. package/dist/devices/oven.d.ts +105 -0
  66. package/dist/devices/oven.d.ts.map +1 -0
  67. package/dist/devices/oven.js +85 -0
  68. package/dist/devices/oven.js.map +1 -0
  69. package/dist/devices/refrigerator.d.ts +118 -0
  70. package/dist/devices/refrigerator.d.ts.map +1 -0
  71. package/dist/devices/refrigerator.js +102 -0
  72. package/dist/devices/refrigerator.js.map +1 -0
  73. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  74. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  75. package/dist/devices/roboticVacuumCleaner.js +100 -9
  76. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  77. package/dist/devices/solarPower.d.ts +40 -0
  78. package/dist/devices/solarPower.d.ts.map +1 -0
  79. package/dist/devices/solarPower.js +38 -0
  80. package/dist/devices/solarPower.js.map +1 -0
  81. package/dist/devices/speaker.d.ts +87 -0
  82. package/dist/devices/speaker.d.ts.map +1 -0
  83. package/dist/devices/speaker.js +84 -0
  84. package/dist/devices/speaker.js.map +1 -0
  85. package/dist/devices/temperatureControl.d.ts +166 -0
  86. package/dist/devices/temperatureControl.d.ts.map +1 -0
  87. package/dist/devices/temperatureControl.js +25 -3
  88. package/dist/devices/temperatureControl.js.map +1 -0
  89. package/dist/devices/waterHeater.d.ts +111 -0
  90. package/dist/devices/waterHeater.d.ts.map +1 -0
  91. package/dist/devices/waterHeater.js +82 -2
  92. package/dist/devices/waterHeater.js.map +1 -0
  93. package/dist/dgram/coap.d.ts +205 -0
  94. package/dist/dgram/coap.d.ts.map +1 -0
  95. package/dist/dgram/coap.js +126 -13
  96. package/dist/dgram/coap.js.map +1 -0
  97. package/dist/dgram/dgram.d.ts +141 -0
  98. package/dist/dgram/dgram.d.ts.map +1 -0
  99. package/dist/dgram/dgram.js +114 -2
  100. package/dist/dgram/dgram.js.map +1 -0
  101. package/dist/dgram/mb_coap.d.ts +24 -0
  102. package/dist/dgram/mb_coap.d.ts.map +1 -0
  103. package/dist/dgram/mb_coap.js +41 -3
  104. package/dist/dgram/mb_coap.js.map +1 -0
  105. package/dist/dgram/mb_mdns.d.ts +24 -0
  106. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  107. package/dist/dgram/mb_mdns.js +80 -15
  108. package/dist/dgram/mb_mdns.js.map +1 -0
  109. package/dist/dgram/mdns.d.ts +290 -0
  110. package/dist/dgram/mdns.d.ts.map +1 -0
  111. package/dist/dgram/mdns.js +299 -137
  112. package/dist/dgram/mdns.js.map +1 -0
  113. package/dist/dgram/multicast.d.ts +67 -0
  114. package/dist/dgram/multicast.d.ts.map +1 -0
  115. package/dist/dgram/multicast.js +62 -1
  116. package/dist/dgram/multicast.js.map +1 -0
  117. package/dist/dgram/unicast.d.ts +56 -0
  118. package/dist/dgram/unicast.d.ts.map +1 -0
  119. package/dist/dgram/unicast.js +54 -0
  120. package/dist/dgram/unicast.js.map +1 -0
  121. package/dist/frontend.d.ts +228 -0
  122. package/dist/frontend.d.ts.map +1 -0
  123. package/dist/frontend.js +429 -38
  124. package/dist/frontend.js.map +1 -0
  125. package/dist/frontendTypes.d.ts +572 -0
  126. package/dist/frontendTypes.d.ts.map +1 -0
  127. package/dist/frontendTypes.js +51 -3
  128. package/dist/frontendTypes.js.map +1 -0
  129. package/dist/globalMatterbridge.d.ts +59 -0
  130. package/dist/globalMatterbridge.d.ts.map +1 -0
  131. package/dist/globalMatterbridge.js +47 -0
  132. package/dist/globalMatterbridge.js.map +1 -0
  133. package/dist/helpers.d.ts +48 -0
  134. package/dist/helpers.d.ts.map +1 -0
  135. package/dist/helpers.js +53 -0
  136. package/dist/helpers.js.map +1 -0
  137. package/dist/index.d.ts +33 -0
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +30 -1
  140. package/dist/index.js.map +1 -0
  141. package/dist/logger/export.d.ts +2 -0
  142. package/dist/logger/export.d.ts.map +1 -0
  143. package/dist/logger/export.js +1 -0
  144. package/dist/logger/export.js.map +1 -0
  145. package/dist/matter/behaviors.d.ts +2 -0
  146. package/dist/matter/behaviors.d.ts.map +1 -0
  147. package/dist/matter/behaviors.js +2 -0
  148. package/dist/matter/behaviors.js.map +1 -0
  149. package/dist/matter/clusters.d.ts +2 -0
  150. package/dist/matter/clusters.d.ts.map +1 -0
  151. package/dist/matter/clusters.js +2 -0
  152. package/dist/matter/clusters.js.map +1 -0
  153. package/dist/matter/devices.d.ts +2 -0
  154. package/dist/matter/devices.d.ts.map +1 -0
  155. package/dist/matter/devices.js +2 -0
  156. package/dist/matter/devices.js.map +1 -0
  157. package/dist/matter/endpoints.d.ts +2 -0
  158. package/dist/matter/endpoints.d.ts.map +1 -0
  159. package/dist/matter/endpoints.js +2 -0
  160. package/dist/matter/endpoints.js.map +1 -0
  161. package/dist/matter/export.d.ts +5 -0
  162. package/dist/matter/export.d.ts.map +1 -0
  163. package/dist/matter/export.js +3 -0
  164. package/dist/matter/export.js.map +1 -0
  165. package/dist/matter/types.d.ts +3 -0
  166. package/dist/matter/types.d.ts.map +1 -0
  167. package/dist/matter/types.js +3 -0
  168. package/dist/matter/types.js.map +1 -0
  169. package/dist/matterbridge.d.ts +442 -0
  170. package/dist/matterbridge.d.ts.map +1 -0
  171. package/dist/matterbridge.js +775 -50
  172. package/dist/matterbridge.js.map +1 -0
  173. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  174. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  175. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  176. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  177. package/dist/matterbridgeBehaviors.d.ts +1747 -0
  178. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  179. package/dist/matterbridgeBehaviors.js +65 -5
  180. package/dist/matterbridgeBehaviors.js.map +1 -0
  181. package/dist/matterbridgeDeviceTypes.d.ts +761 -0
  182. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  183. package/dist/matterbridgeDeviceTypes.js +630 -17
  184. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  185. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  186. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  187. package/dist/matterbridgeDynamicPlatform.js +36 -0
  188. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  189. package/dist/matterbridgeEndpoint.d.ts +1515 -0
  190. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  191. package/dist/matterbridgeEndpoint.js +1373 -55
  192. package/dist/matterbridgeEndpoint.js.map +1 -0
  193. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  194. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  195. package/dist/matterbridgeEndpointHelpers.js +345 -12
  196. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  197. package/dist/matterbridgePlatform.d.ts +380 -0
  198. package/dist/matterbridgePlatform.d.ts.map +1 -0
  199. package/dist/matterbridgePlatform.js +304 -0
  200. package/dist/matterbridgePlatform.js.map +1 -0
  201. package/dist/matterbridgeTypes.d.ts +190 -0
  202. package/dist/matterbridgeTypes.d.ts.map +1 -0
  203. package/dist/matterbridgeTypes.js +25 -0
  204. package/dist/matterbridgeTypes.js.map +1 -0
  205. package/dist/pluginManager.d.ts +270 -0
  206. package/dist/pluginManager.d.ts.map +1 -0
  207. package/dist/pluginManager.js +249 -3
  208. package/dist/pluginManager.js.map +1 -0
  209. package/dist/shelly.d.ts +174 -0
  210. package/dist/shelly.d.ts.map +1 -0
  211. package/dist/shelly.js +172 -11
  212. package/dist/shelly.js.map +1 -0
  213. package/dist/storage/export.d.ts +2 -0
  214. package/dist/storage/export.d.ts.map +1 -0
  215. package/dist/storage/export.js +1 -0
  216. package/dist/storage/export.js.map +1 -0
  217. package/dist/update.d.ts +75 -0
  218. package/dist/update.d.ts.map +1 -0
  219. package/dist/update.js +69 -0
  220. package/dist/update.js.map +1 -0
  221. package/dist/utils/colorUtils.d.ts +99 -0
  222. package/dist/utils/colorUtils.d.ts.map +1 -0
  223. package/dist/utils/colorUtils.js +97 -2
  224. package/dist/utils/colorUtils.js.map +1 -0
  225. package/dist/utils/commandLine.d.ts +59 -0
  226. package/dist/utils/commandLine.d.ts.map +1 -0
  227. package/dist/utils/commandLine.js +54 -0
  228. package/dist/utils/commandLine.js.map +1 -0
  229. package/dist/utils/copyDirectory.d.ts +33 -0
  230. package/dist/utils/copyDirectory.d.ts.map +1 -0
  231. package/dist/utils/copyDirectory.js +38 -1
  232. package/dist/utils/copyDirectory.js.map +1 -0
  233. package/dist/utils/createDirectory.d.ts +34 -0
  234. package/dist/utils/createDirectory.d.ts.map +1 -0
  235. package/dist/utils/createDirectory.js +33 -0
  236. package/dist/utils/createDirectory.js.map +1 -0
  237. package/dist/utils/createZip.d.ts +39 -0
  238. package/dist/utils/createZip.d.ts.map +1 -0
  239. package/dist/utils/createZip.js +47 -2
  240. package/dist/utils/createZip.js.map +1 -0
  241. package/dist/utils/deepCopy.d.ts +32 -0
  242. package/dist/utils/deepCopy.d.ts.map +1 -0
  243. package/dist/utils/deepCopy.js +39 -0
  244. package/dist/utils/deepCopy.js.map +1 -0
  245. package/dist/utils/deepEqual.d.ts +54 -0
  246. package/dist/utils/deepEqual.d.ts.map +1 -0
  247. package/dist/utils/deepEqual.js +72 -1
  248. package/dist/utils/deepEqual.js.map +1 -0
  249. package/dist/utils/error.d.ts +44 -0
  250. package/dist/utils/error.d.ts.map +1 -0
  251. package/dist/utils/error.js +41 -0
  252. package/dist/utils/error.js.map +1 -0
  253. package/dist/utils/export.d.ts +13 -0
  254. package/dist/utils/export.d.ts.map +1 -0
  255. package/dist/utils/export.js +1 -0
  256. package/dist/utils/export.js.map +1 -0
  257. package/dist/utils/hex.d.ts +89 -0
  258. package/dist/utils/hex.d.ts.map +1 -0
  259. package/dist/utils/hex.js +124 -0
  260. package/dist/utils/hex.js.map +1 -0
  261. package/dist/utils/isvalid.d.ts +103 -0
  262. package/dist/utils/isvalid.d.ts.map +1 -0
  263. package/dist/utils/isvalid.js +101 -0
  264. package/dist/utils/isvalid.js.map +1 -0
  265. package/dist/utils/jestHelpers.d.ts +137 -0
  266. package/dist/utils/jestHelpers.d.ts.map +1 -0
  267. package/dist/utils/jestHelpers.js +153 -3
  268. package/dist/utils/jestHelpers.js.map +1 -0
  269. package/dist/utils/network.d.ts +84 -0
  270. package/dist/utils/network.d.ts.map +1 -0
  271. package/dist/utils/network.js +91 -5
  272. package/dist/utils/network.js.map +1 -0
  273. package/dist/utils/spawn.d.ts +33 -0
  274. package/dist/utils/spawn.d.ts.map +1 -0
  275. package/dist/utils/spawn.js +68 -0
  276. package/dist/utils/spawn.js.map +1 -0
  277. package/dist/utils/wait.d.ts +54 -0
  278. package/dist/utils/wait.d.ts.map +1 -0
  279. package/dist/utils/wait.js +60 -8
  280. package/dist/utils/wait.js.map +1 -0
  281. package/frontend/build/assets/index.js +1 -1
  282. package/frontend/build/index.html +12 -12
  283. package/npm-shrinkwrap.json +2 -2
  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,13 +29,17 @@ 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';
42
+ // Matterbridge
15
43
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
16
44
  import { plg } from './matterbridgeTypes.js';
17
45
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
@@ -27,7 +55,7 @@ export class Frontend extends EventEmitter {
27
55
  constructor(matterbridge) {
28
56
  super();
29
57
  this.matterbridge = matterbridge;
30
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
58
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
31
59
  this.log.logNameColor = '\x1b[38;5;97m';
32
60
  }
33
61
  set logLevel(logLevel) {
@@ -36,10 +64,39 @@ export class Frontend extends EventEmitter {
36
64
  async start(port = 8283) {
37
65
  this.port = port;
38
66
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
39
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
67
+ // Initialize multer with the upload directory
68
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
40
69
  const upload = multer({ dest: uploadDir });
70
+ // Create the express app that serves the frontend
41
71
  this.expressApp = express();
72
+ // Inject logging/debug wrapper for route/middleware registration
73
+ /*
74
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
75
+ for (const method of methods) {
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
80
+ try {
81
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
82
+ return original(path, ...rest);
83
+ } catch (err) {
84
+ console.error(`[ERROR] Failed to register route: ${path}`);
85
+ throw err;
86
+ }
87
+ };
88
+ }
89
+ */
90
+ // Log all requests to the server for debugging
91
+ /*
92
+ this.expressApp.use((req, res, next) => {
93
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
94
+ next();
95
+ });
96
+ */
97
+ // Serve static files from '/static' endpoint
42
98
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
99
+ // Read the package.json file to get the frontend version
43
100
  try {
44
101
  this.log.debug(`Reading frontend package.json...`);
45
102
  const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
@@ -47,9 +104,11 @@ export class Frontend extends EventEmitter {
47
104
  this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
48
105
  }
49
106
  catch (error) {
107
+ // istanbul ignore next
50
108
  this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
51
109
  }
52
110
  if (!hasParameter('ssl')) {
111
+ // Create an HTTP server and attach the express app
53
112
  try {
54
113
  this.log.debug(`Creating HTTP server...`);
55
114
  this.httpServer = createServer(this.expressApp);
@@ -59,6 +118,7 @@ export class Frontend extends EventEmitter {
59
118
  this.emit('server_error', error);
60
119
  return;
61
120
  }
121
+ // Listen on the specified port
62
122
  if (hasParameter('ingress')) {
63
123
  this.httpServer.listen(this.port, '0.0.0.0', () => {
64
124
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -97,6 +157,7 @@ export class Frontend extends EventEmitter {
97
157
  let passphrase;
98
158
  let httpsServerOptions = {};
99
159
  if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
160
+ // Load the p12 certificate and the passphrase
100
161
  try {
101
162
  pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
102
163
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -108,7 +169,7 @@ export class Frontend extends EventEmitter {
108
169
  }
109
170
  try {
110
171
  passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
111
- passphrase = passphrase.trim();
172
+ passphrase = passphrase.trim(); // Ensure no extra characters
112
173
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
113
174
  }
114
175
  catch (error) {
@@ -119,6 +180,7 @@ export class Frontend extends EventEmitter {
119
180
  httpsServerOptions = { pfx, passphrase };
120
181
  }
121
182
  else {
183
+ // 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.
122
184
  try {
123
185
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
124
186
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -148,9 +210,10 @@ export class Frontend extends EventEmitter {
148
210
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
149
211
  }
150
212
  if (hasParameter('mtls')) {
151
- httpsServerOptions.requestCert = true;
152
- httpsServerOptions.rejectUnauthorized = true;
213
+ httpsServerOptions.requestCert = true; // Request client certificate
214
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
153
215
  }
216
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
154
217
  try {
155
218
  this.log.debug(`Creating HTTPS server...`);
156
219
  this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
@@ -160,6 +223,7 @@ export class Frontend extends EventEmitter {
160
223
  this.emit('server_error', error);
161
224
  return;
162
225
  }
226
+ // Listen on the specified port
163
227
  if (hasParameter('ingress')) {
164
228
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
165
229
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -189,17 +253,19 @@ export class Frontend extends EventEmitter {
189
253
  return;
190
254
  });
191
255
  }
256
+ // Create a WebSocket server and attach it to the http or https server
192
257
  const wssPort = this.port;
193
258
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
194
259
  this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
195
260
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
196
261
  this.webSocketServer.on('connection', (ws, request) => {
197
262
  const clientIp = request.socket.remoteAddress;
198
- let callbackLogLevel = "notice";
199
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
200
- callbackLogLevel = "info";
201
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
202
- callbackLogLevel = "debug";
263
+ // Set the global logger callback for the WebSocketServer
264
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
265
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
266
+ callbackLogLevel = "info" /* LogLevel.INFO */;
267
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
268
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
203
269
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
204
270
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
205
271
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -221,6 +287,7 @@ export class Frontend extends EventEmitter {
221
287
  }
222
288
  });
223
289
  ws.on('error', (error) => {
290
+ // istanbul ignore next
224
291
  this.log.error(`WebSocket client error: ${error}`);
225
292
  });
226
293
  });
@@ -234,6 +301,7 @@ export class Frontend extends EventEmitter {
234
301
  this.webSocketServer.on('error', (ws, error) => {
235
302
  this.log.error(`WebSocketServer error: ${error}`);
236
303
  });
304
+ // Subscribe to cli events
237
305
  cliEmitter.removeAllListeners();
238
306
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
239
307
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -244,6 +312,8 @@ export class Frontend extends EventEmitter {
244
312
  cliEmitter.on('cpu', (cpuUsage) => {
245
313
  this.wssSendCpuUpdate(cpuUsage);
246
314
  });
315
+ // Endpoint to validate login code
316
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
247
317
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
248
318
  const { password } = req.body;
249
319
  this.log.debug('The frontend sent /api/login', password);
@@ -262,23 +332,27 @@ export class Frontend extends EventEmitter {
262
332
  this.log.warn('/api/login error wrong password');
263
333
  res.json({ valid: false });
264
334
  }
335
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
265
336
  }
266
337
  catch (error) {
267
338
  this.log.error('/api/login error getting password');
268
339
  res.json({ valid: false });
269
340
  }
270
341
  });
342
+ // Endpoint to provide health check for docker
271
343
  this.expressApp.get('/health', (req, res) => {
272
344
  this.log.debug('Express received /health');
273
345
  const healthStatus = {
274
- status: 'ok',
275
- uptime: process.uptime(),
276
- timestamp: new Date().toISOString(),
346
+ status: 'ok', // Indicate service is healthy
347
+ uptime: process.uptime(), // Server uptime in seconds
348
+ timestamp: new Date().toISOString(), // Current timestamp
277
349
  };
278
350
  res.status(200).json(healthStatus);
279
351
  });
352
+ // Endpoint to provide memory usage details
280
353
  this.expressApp.get('/memory', async (req, res) => {
281
354
  this.log.debug('Express received /memory');
355
+ // Memory usage from process
282
356
  const memoryUsageRaw = process.memoryUsage();
283
357
  const memoryUsage = {
284
358
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -287,10 +361,13 @@ export class Frontend extends EventEmitter {
287
361
  external: this.formatMemoryUsage(memoryUsageRaw.external),
288
362
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
289
363
  };
364
+ // V8 heap statistics
290
365
  const { default: v8 } = await import('node:v8');
291
366
  const heapStatsRaw = v8.getHeapStatistics();
292
367
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
368
+ // Format heapStats
293
369
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
370
+ // Format heapSpaces
294
371
  const heapSpaces = heapSpacesRaw.map((space) => ({
295
372
  ...space,
296
373
  space_size: this.formatMemoryUsage(space.space_size),
@@ -308,19 +385,23 @@ export class Frontend extends EventEmitter {
308
385
  };
309
386
  res.status(200).json(memoryReport);
310
387
  });
388
+ // Endpoint to provide settings
311
389
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
312
390
  this.log.debug('The frontend sent /api/settings');
313
391
  res.json(await this.getApiSettings());
314
392
  });
393
+ // Endpoint to provide plugins
315
394
  this.expressApp.get('/api/plugins', async (req, res) => {
316
395
  this.log.debug('The frontend sent /api/plugins');
317
396
  res.json(this.getPlugins());
318
397
  });
398
+ // Endpoint to provide devices
319
399
  this.expressApp.get('/api/devices', async (req, res) => {
320
400
  this.log.debug('The frontend sent /api/devices');
321
401
  const devices = await this.getDevices();
322
402
  res.json(devices);
323
403
  });
404
+ // Endpoint to view the matterbridge log
324
405
  this.expressApp.get('/api/view-mblog', async (req, res) => {
325
406
  this.log.debug('The frontend sent /api/view-mblog');
326
407
  try {
@@ -333,6 +414,7 @@ export class Frontend extends EventEmitter {
333
414
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
334
415
  }
335
416
  });
417
+ // Endpoint to view the matter.js log
336
418
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
337
419
  this.log.debug('The frontend sent /api/view-mjlog');
338
420
  try {
@@ -345,9 +427,11 @@ export class Frontend extends EventEmitter {
345
427
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
346
428
  }
347
429
  });
430
+ // Endpoint to view the diagnostic.log
348
431
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
349
432
  this.log.debug('The frontend sent /api/view-diagnostic');
350
433
  const serverNodes = [];
434
+ // istanbul ignore else
351
435
  if (this.matterbridge.bridgeMode === 'bridge') {
352
436
  if (this.matterbridge.serverNode)
353
437
  serverNodes.push(this.matterbridge.serverNode);
@@ -358,6 +442,7 @@ export class Frontend extends EventEmitter {
358
442
  serverNodes.push(plugin.serverNode);
359
443
  }
360
444
  }
445
+ // istanbul ignore next
361
446
  for (const device of this.matterbridge.getDevices()) {
362
447
  if (device.serverNode)
363
448
  serverNodes.push(device.serverNode);
@@ -380,17 +465,20 @@ export class Frontend extends EventEmitter {
380
465
  values: [...serverNodes],
381
466
  })));
382
467
  delete Logger.destinations.diagnostic;
383
- await wait(500);
468
+ await wait(500); // Wait for the log to be written
384
469
  try {
385
470
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
386
471
  res.type('text/plain');
387
472
  res.send(data.slice(29));
388
473
  }
389
474
  catch (error) {
475
+ // istanbul ignore next
390
476
  this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
477
+ // istanbul ignore next
391
478
  res.status(500).send('Error reading diagnostic log file.');
392
479
  }
393
480
  });
481
+ // Endpoint to view the shelly log
394
482
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
395
483
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
396
484
  try {
@@ -403,6 +491,7 @@ export class Frontend extends EventEmitter {
403
491
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
404
492
  }
405
493
  });
494
+ // Endpoint to download the matterbridge log
406
495
  this.expressApp.get('/api/download-mblog', async (req, res) => {
407
496
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
408
497
  try {
@@ -416,12 +505,14 @@ export class Frontend extends EventEmitter {
416
505
  }
417
506
  res.type('text/plain');
418
507
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
508
+ /* istanbul ignore if */
419
509
  if (error) {
420
510
  this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
421
511
  res.status(500).send('Error downloading the matterbridge log file');
422
512
  }
423
513
  });
424
514
  });
515
+ // Endpoint to download the matter log
425
516
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
426
517
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
427
518
  try {
@@ -435,12 +526,14 @@ export class Frontend extends EventEmitter {
435
526
  }
436
527
  res.type('text/plain');
437
528
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
529
+ /* istanbul ignore if */
438
530
  if (error) {
439
531
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
440
532
  res.status(500).send('Error downloading the matter log file');
441
533
  }
442
534
  });
443
535
  });
536
+ // Endpoint to download the shelly log
444
537
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
445
538
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
446
539
  try {
@@ -454,74 +547,90 @@ export class Frontend extends EventEmitter {
454
547
  }
455
548
  res.type('text/plain');
456
549
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
550
+ /* istanbul ignore if */
457
551
  if (error) {
458
552
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
459
553
  res.status(500).send('Error downloading Shelly system log file');
460
554
  }
461
555
  });
462
556
  });
557
+ // Endpoint to download the matterbridge storage directory
463
558
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
464
559
  this.log.debug('The frontend sent /api/download-mbstorage');
465
560
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
466
561
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
562
+ /* istanbul ignore if */
467
563
  if (error) {
468
564
  this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
469
565
  res.status(500).send('Error downloading the matterbridge storage file');
470
566
  }
471
567
  });
472
568
  });
569
+ // Endpoint to download the matter storage file
473
570
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
474
571
  this.log.debug('The frontend sent /api/download-mjstorage');
475
572
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
476
573
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
574
+ /* istanbul ignore if */
477
575
  if (error) {
478
576
  this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
479
577
  res.status(500).send('Error downloading the matter storage zip file');
480
578
  }
481
579
  });
482
580
  });
581
+ // Endpoint to download the matterbridge plugin directory
483
582
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
484
583
  this.log.debug('The frontend sent /api/download-pluginstorage');
485
584
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
486
585
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
586
+ /* istanbul ignore if */
487
587
  if (error) {
488
588
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
489
589
  res.status(500).send('Error downloading the matterbridge plugin storage file');
490
590
  }
491
591
  });
492
592
  });
593
+ // Endpoint to download the matterbridge plugin config files
493
594
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
494
595
  this.log.debug('The frontend sent /api/download-pluginconfig');
495
596
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
496
597
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
598
+ /* istanbul ignore if */
497
599
  if (error) {
498
600
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
499
601
  res.status(500).send('Error downloading the matterbridge plugin config file');
500
602
  }
501
603
  });
502
604
  });
605
+ // Endpoint to download the matterbridge backup (created with the backup command)
503
606
  this.expressApp.get('/api/download-backup', async (req, res) => {
504
607
  this.log.debug('The frontend sent /api/download-backup');
505
608
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
609
+ /* istanbul ignore if */
506
610
  if (error) {
507
611
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
508
612
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
509
613
  }
510
614
  });
511
615
  });
616
+ // Endpoint to upload a package
512
617
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
513
618
  const { filename } = req.body;
514
619
  const file = req.file;
620
+ /* istanbul ignore if */
515
621
  if (!file || !filename) {
516
622
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
517
623
  res.status(400).send('Invalid request: file and filename are required');
518
624
  return;
519
625
  }
520
626
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
627
+ // Define the path where the plugin file will be saved
521
628
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
522
629
  try {
630
+ // Move the uploaded file to the specified path
523
631
  await fs.rename(file.path, filePath);
524
632
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
633
+ // Install the plugin package
525
634
  if (filename.endsWith('.tgz')) {
526
635
  const { spawnCommand } = await import('./utils/spawn.js');
527
636
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
@@ -541,6 +650,7 @@ export class Frontend extends EventEmitter {
541
650
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
542
651
  }
543
652
  });
653
+ // Fallback for routing (must be the last route)
544
654
  this.expressApp.use((req, res) => {
545
655
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
546
656
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -549,13 +659,16 @@ export class Frontend extends EventEmitter {
549
659
  }
550
660
  async stop() {
551
661
  this.log.debug('Stopping the frontend...');
662
+ // Remove listeners from the express app
552
663
  if (this.expressApp) {
553
664
  this.expressApp.removeAllListeners();
554
665
  this.expressApp = undefined;
555
666
  this.log.debug('Frontend app closed successfully');
556
667
  }
668
+ // Close the WebSocket server
557
669
  if (this.webSocketServer) {
558
670
  this.log.debug('Closing WebSocket server...');
671
+ // Close all active connections
559
672
  this.webSocketServer.clients.forEach((client) => {
560
673
  if (client.readyState === WebSocket.OPEN) {
561
674
  client.close();
@@ -564,6 +677,7 @@ export class Frontend extends EventEmitter {
564
677
  await withTimeout(new Promise((resolve) => {
565
678
  this.webSocketServer?.close((error) => {
566
679
  if (error) {
680
+ // istanbul ignore next
567
681
  this.log.error(`Error closing WebSocket server: ${error}`);
568
682
  }
569
683
  else {
@@ -576,8 +690,27 @@ export class Frontend extends EventEmitter {
576
690
  this.webSocketServer.removeAllListeners();
577
691
  this.webSocketServer = undefined;
578
692
  }
693
+ // Close the http server
579
694
  if (this.httpServer) {
580
695
  this.log.debug('Closing http server...');
696
+ /*
697
+ await withTimeout(
698
+ new Promise<void>((resolve) => {
699
+ this.httpServer?.close((error) => {
700
+ if (error) {
701
+ // istanbul ignore next
702
+ this.log.error(`Error closing http server: ${error}`);
703
+ } else {
704
+ this.log.debug('Http server closed successfully');
705
+ this.emit('server_stopped');
706
+ }
707
+ resolve();
708
+ });
709
+ }),
710
+ 5000,
711
+ false,
712
+ );
713
+ */
581
714
  this.httpServer.close();
582
715
  this.log.debug('Http server closed successfully');
583
716
  this.emit('server_stopped');
@@ -585,8 +718,27 @@ export class Frontend extends EventEmitter {
585
718
  this.httpServer = undefined;
586
719
  this.log.debug('Frontend http server closed successfully');
587
720
  }
721
+ // Close the https server
588
722
  if (this.httpsServer) {
589
723
  this.log.debug('Closing https server...');
724
+ /*
725
+ await withTimeout(
726
+ new Promise<void>((resolve) => {
727
+ this.httpsServer?.close((error) => {
728
+ if (error) {
729
+ // istanbul ignore next
730
+ this.log.error(`Error closing https server: ${error}`);
731
+ } else {
732
+ this.log.debug('Https server closed successfully');
733
+ this.emit('server_stopped');
734
+ }
735
+ resolve();
736
+ });
737
+ }),
738
+ 5000,
739
+ false,
740
+ );
741
+ */
590
742
  this.httpsServer.close();
591
743
  this.log.debug('Https server closed successfully');
592
744
  this.emit('server_stopped');
@@ -596,6 +748,7 @@ export class Frontend extends EventEmitter {
596
748
  }
597
749
  this.log.debug('Frontend stopped successfully');
598
750
  }
751
+ // Function to format bytes to KB, MB, or GB
599
752
  formatMemoryUsage = (bytes) => {
600
753
  if (bytes >= 1024 ** 3) {
601
754
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -607,6 +760,7 @@ export class Frontend extends EventEmitter {
607
760
  return `${(bytes / 1024).toFixed(2)} KB`;
608
761
  }
609
762
  };
763
+ // Function to format system uptime with only the most significant unit
610
764
  formatOsUpTime = (seconds) => {
611
765
  if (seconds >= 86400) {
612
766
  const days = Math.floor(seconds / 86400);
@@ -622,7 +776,13 @@ export class Frontend extends EventEmitter {
622
776
  }
623
777
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
624
778
  };
779
+ /**
780
+ * Retrieves the api settings data.
781
+ *
782
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
783
+ */
625
784
  async getApiSettings() {
785
+ // Update the system information
626
786
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
627
787
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
628
788
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -631,6 +791,7 @@ export class Frontend extends EventEmitter {
631
791
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
632
792
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
633
793
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
794
+ // Update the matterbridge information
634
795
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
635
796
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
636
797
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
@@ -644,6 +805,12 @@ export class Frontend extends EventEmitter {
644
805
  this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
645
806
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
646
807
  }
808
+ /**
809
+ * Retrieves the reachable attribute.
810
+ *
811
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
812
+ * @returns {boolean} The reachable attribute.
813
+ */
647
814
  getReachability(device) {
648
815
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
649
816
  return false;
@@ -655,6 +822,12 @@ export class Frontend extends EventEmitter {
655
822
  return true;
656
823
  return false;
657
824
  }
825
+ /**
826
+ * Retrieves the power source attribute.
827
+ *
828
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
829
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
830
+ */
658
831
  getPowerSource(endpoint) {
659
832
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
660
833
  return undefined;
@@ -670,13 +843,22 @@ export class Frontend extends EventEmitter {
670
843
  }
671
844
  return;
672
845
  };
846
+ // Root endpoint
673
847
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
674
848
  return powerSource(endpoint);
849
+ // Child endpoints
675
850
  for (const child of endpoint.getChildEndpoints()) {
676
851
  if (child.hasClusterServer(PowerSource.Cluster.id))
677
852
  return powerSource(child);
678
853
  }
679
854
  }
855
+ /**
856
+ * Retrieves the cluster text description from a given device.
857
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
858
+ *
859
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
860
+ * @returns {string} The attributes description of the cluster servers in the device.
861
+ */
680
862
  getClusterTextFromDevice(device) {
681
863
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
682
864
  return '';
@@ -687,6 +869,7 @@ export class Frontend extends EventEmitter {
687
869
  if (composed)
688
870
  return 'Composed: ' + composed.value;
689
871
  }
872
+ // istanbul ignore next cause is not reachable
690
873
  return '';
691
874
  };
692
875
  const getFixedLabel = (device) => {
@@ -696,11 +879,13 @@ export class Frontend extends EventEmitter {
696
879
  if (composed)
697
880
  return 'Composed: ' + composed.value;
698
881
  }
882
+ // istanbul ignore next cause is not reacheable
699
883
  return '';
700
884
  };
701
885
  let attributes = '';
702
886
  let supportedModes = [];
703
887
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
888
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
704
889
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
705
890
  return;
706
891
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -790,11 +975,17 @@ export class Frontend extends EventEmitter {
790
975
  if (clusterName === 'userLabel' && attributeName === 'labelList')
791
976
  attributes += `${getUserLabel(device)} `;
792
977
  });
978
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
793
979
  return attributes.trimStart().trimEnd();
794
980
  }
981
+ /**
982
+ * Retrieves the registered plugins sanitized for res.json().
983
+ *
984
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
985
+ */
795
986
  getPlugins() {
796
987
  if (this.matterbridge.hasCleanupStarted)
797
- return [];
988
+ return []; // Skip if cleanup has started
798
989
  const baseRegisteredPlugins = [];
799
990
  for (const plugin of this.matterbridge.plugins) {
800
991
  baseRegisteredPlugins.push({
@@ -822,18 +1013,27 @@ export class Frontend extends EventEmitter {
822
1013
  schemaJson: plugin.schemaJson,
823
1014
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
824
1015
  hasBlackList: plugin.configJson?.blackList !== undefined,
1016
+ // Childbridge mode specific data
825
1017
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
826
1018
  });
827
1019
  }
828
1020
  return baseRegisteredPlugins;
829
1021
  }
1022
+ /**
1023
+ * Retrieves the devices from Matterbridge.
1024
+ *
1025
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1026
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1027
+ */
830
1028
  async getDevices(pluginName) {
831
1029
  if (this.matterbridge.hasCleanupStarted)
832
- return [];
1030
+ return []; // Skip if cleanup has started
833
1031
  const devices = [];
834
1032
  for (const device of this.matterbridge.devices.array()) {
1033
+ // Filter by pluginName if provided
835
1034
  if (pluginName && pluginName !== device.plugin)
836
1035
  continue;
1036
+ // Check if the device has the required properties
837
1037
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
838
1038
  continue;
839
1039
  devices.push({
@@ -853,22 +1053,37 @@ export class Frontend extends EventEmitter {
853
1053
  }
854
1054
  return devices;
855
1055
  }
1056
+ /**
1057
+ * Retrieves the clusters from a given plugin and endpoint number.
1058
+ *
1059
+ * Response for /api/clusters
1060
+ *
1061
+ * @param {string} pluginName - The name of the plugin.
1062
+ * @param {number} endpointNumber - The endpoint number.
1063
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1064
+ */
856
1065
  getClusters(pluginName, endpointNumber) {
857
1066
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
858
1067
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
859
1068
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
860
1069
  return;
861
1070
  }
1071
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1072
+ // Get the device types from the main endpoint
862
1073
  const deviceTypes = [];
863
1074
  const clusters = [];
864
1075
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
865
1076
  deviceTypes.push(d.deviceType);
866
1077
  });
1078
+ // Get the clusters from the main endpoint
867
1079
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
868
1080
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
869
1081
  return;
870
1082
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
871
1083
  return;
1084
+ // console.log(
1085
+ // `${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}`,
1086
+ // );
872
1087
  clusters.push({
873
1088
  endpoint: endpoint.number.toString(),
874
1089
  id: 'main',
@@ -881,12 +1096,19 @@ export class Frontend extends EventEmitter {
881
1096
  attributeLocalValue: attributeValue,
882
1097
  });
883
1098
  });
1099
+ // Get the child endpoints
884
1100
  const childEndpoints = endpoint.getChildEndpoints();
1101
+ // if (childEndpoints.length === 0) {
1102
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1103
+ // }
885
1104
  childEndpoints.forEach((childEndpoint) => {
1105
+ // istanbul ignore if cause is not reachable: should never happen but ...
886
1106
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
887
1107
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
888
1108
  return;
889
1109
  }
1110
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1111
+ // Get the device types of the child endpoint
890
1112
  const deviceTypes = [];
891
1113
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
892
1114
  deviceTypes.push(d.deviceType);
@@ -896,9 +1118,12 @@ export class Frontend extends EventEmitter {
896
1118
  return;
897
1119
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
898
1120
  return;
1121
+ // console.log(
1122
+ // `${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}`,
1123
+ // );
899
1124
  clusters.push({
900
1125
  endpoint: childEndpoint.number.toString(),
901
- id: childEndpoint.maybeId ?? 'null',
1126
+ id: childEndpoint.maybeId ?? 'null', // Never happens
902
1127
  deviceTypes,
903
1128
  clusterName: capitalizeFirstLetter(clusterName),
904
1129
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -911,6 +1136,13 @@ export class Frontend extends EventEmitter {
911
1136
  });
912
1137
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
913
1138
  }
1139
+ /**
1140
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1141
+ *
1142
+ * @param {WebSocket} client - The websocket client that sent the message.
1143
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1144
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1145
+ */
914
1146
  async wsMessageHandler(client, message) {
915
1147
  let data;
916
1148
  const sendResponse = (data) => {
@@ -930,7 +1162,7 @@ export class Frontend extends EventEmitter {
930
1162
  };
931
1163
  try {
932
1164
  data = JSON.parse(message.toString());
933
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1165
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
934
1166
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
935
1167
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
936
1168
  return;
@@ -973,35 +1205,48 @@ export class Frontend extends EventEmitter {
973
1205
  this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
974
1206
  const packageName = localData.params.packageName.replace(/@.*$/, '');
975
1207
  if (localData.params.restart === false && packageName !== 'matterbridge') {
1208
+ // The install comes from InstallPlugins
976
1209
  this.matterbridge.plugins
977
1210
  .add(packageName)
978
1211
  .then((plugin) => {
1212
+ // istanbul ignore next if
979
1213
  if (plugin) {
1214
+ // The plugin is not registered
980
1215
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1216
+ // In childbridge mode the plugins server node is not started when added
981
1217
  if (this.matterbridge.bridgeMode === 'childbridge')
982
1218
  this.wssSendRestartRequired(true, true);
983
1219
  this.matterbridge.plugins
984
1220
  .load(plugin, true, 'The plugin has been added', true)
1221
+ // eslint-disable-next-line promise/no-nesting
985
1222
  .then(() => {
986
1223
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
987
1224
  this.wssSendRefreshRequired('plugins');
988
1225
  return;
989
1226
  })
1227
+ // eslint-disable-next-line promise/no-nesting
990
1228
  .catch((_error) => {
1229
+ //
991
1230
  });
992
1231
  }
993
1232
  else {
1233
+ // The plugin is already registered
994
1234
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
995
1235
  this.wssSendRefreshRequired('plugins');
996
1236
  this.wssSendRestartRequired(true, true);
997
1237
  }
998
1238
  return;
999
1239
  })
1240
+ // eslint-disable-next-line promise/no-nesting
1000
1241
  .catch((_error) => {
1242
+ //
1001
1243
  });
1002
1244
  }
1003
1245
  else {
1246
+ // The package is matterbridge
1247
+ // istanbul ignore next
1004
1248
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1249
+ // istanbul ignore next if
1005
1250
  if (this.matterbridge.restartMode !== '') {
1006
1251
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1007
1252
  this.matterbridge.shutdownProcess();
@@ -1024,7 +1269,9 @@ export class Frontend extends EventEmitter {
1024
1269
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
1025
1270
  return;
1026
1271
  }
1272
+ // The package is a plugin
1027
1273
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1274
+ // istanbul ignore next if
1028
1275
  if (plugin) {
1029
1276
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1030
1277
  await this.matterbridge.plugins.remove(data.params.packageName);
@@ -1032,6 +1279,7 @@ export class Frontend extends EventEmitter {
1032
1279
  this.wssSendRefreshRequired('plugins');
1033
1280
  this.wssSendRefreshRequired('devices');
1034
1281
  }
1282
+ // Uninstall the package
1035
1283
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1036
1284
  const { spawnCommand } = await import('./utils/spawn.js');
1037
1285
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1074,6 +1322,7 @@ export class Frontend extends EventEmitter {
1074
1322
  return;
1075
1323
  })
1076
1324
  .catch((_error) => {
1325
+ //
1077
1326
  });
1078
1327
  }
1079
1328
  else {
@@ -1121,6 +1370,7 @@ export class Frontend extends EventEmitter {
1121
1370
  return;
1122
1371
  })
1123
1372
  .catch((_error) => {
1373
+ //
1124
1374
  });
1125
1375
  }
1126
1376
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1146,6 +1396,7 @@ export class Frontend extends EventEmitter {
1146
1396
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1147
1397
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1148
1398
  if (plugin.serverNode) {
1399
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1149
1400
  await this.matterbridge.stopServerNode(plugin.serverNode);
1150
1401
  plugin.serverNode = undefined;
1151
1402
  }
@@ -1155,18 +1406,20 @@ export class Frontend extends EventEmitter {
1155
1406
  this.matterbridge.devices.remove(device);
1156
1407
  }
1157
1408
  }
1409
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1158
1410
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1159
1411
  await this.matterbridge.createDynamicPlugin(plugin);
1160
1412
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1161
- plugin.restartRequired = false;
1413
+ plugin.restartRequired = false; // Reset plugin restartRequired
1162
1414
  let needRestart = 0;
1163
1415
  for (const plugin of this.matterbridge.plugins) {
1164
1416
  if (plugin.restartRequired)
1165
1417
  needRestart++;
1166
1418
  }
1167
1419
  if (needRestart === 0) {
1168
- this.wssSendRestartNotRequired(true);
1420
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1169
1421
  }
1422
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1170
1423
  if (plugin.serverNode)
1171
1424
  await this.matterbridge.startServerNode(plugin.serverNode);
1172
1425
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1422,22 +1675,22 @@ export class Frontend extends EventEmitter {
1422
1675
  if (isValidString(data.params.value, 4)) {
1423
1676
  this.log.debug('Matterbridge logger level:', data.params.value);
1424
1677
  if (data.params.value === 'Debug') {
1425
- await this.matterbridge.setLogLevel("debug");
1678
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1426
1679
  }
1427
1680
  else if (data.params.value === 'Info') {
1428
- await this.matterbridge.setLogLevel("info");
1681
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1429
1682
  }
1430
1683
  else if (data.params.value === 'Notice') {
1431
- await this.matterbridge.setLogLevel("notice");
1684
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1432
1685
  }
1433
1686
  else if (data.params.value === 'Warn') {
1434
- await this.matterbridge.setLogLevel("warn");
1687
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1435
1688
  }
1436
1689
  else if (data.params.value === 'Error') {
1437
- await this.matterbridge.setLogLevel("error");
1690
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1438
1691
  }
1439
1692
  else if (data.params.value === 'Fatal') {
1440
- await this.matterbridge.setLogLevel("fatal");
1693
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1441
1694
  }
1442
1695
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1443
1696
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1448,6 +1701,7 @@ export class Frontend extends EventEmitter {
1448
1701
  this.log.debug('Matterbridge file log:', data.params.value);
1449
1702
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1450
1703
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1704
+ // Create the file logger for matterbridge
1451
1705
  if (data.params.value)
1452
1706
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1453
1707
  else
@@ -1526,6 +1780,7 @@ export class Frontend extends EventEmitter {
1526
1780
  }
1527
1781
  break;
1528
1782
  case 'setmatterport':
1783
+ // eslint-disable-next-line no-case-declarations
1529
1784
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1530
1785
  if (isValidNumber(port, 5540, 5600)) {
1531
1786
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1543,6 +1798,7 @@ export class Frontend extends EventEmitter {
1543
1798
  }
1544
1799
  break;
1545
1800
  case 'setmatterdiscriminator':
1801
+ // eslint-disable-next-line no-case-declarations
1546
1802
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1547
1803
  if (isValidNumber(discriminator, 1000, 4095)) {
1548
1804
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1560,6 +1816,7 @@ export class Frontend extends EventEmitter {
1560
1816
  }
1561
1817
  break;
1562
1818
  case 'setmatterpasscode':
1819
+ // eslint-disable-next-line no-case-declarations
1563
1820
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1564
1821
  if (isValidNumber(passcode, 10000000, 90000000)) {
1565
1822
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -1603,15 +1860,19 @@ export class Frontend extends EventEmitter {
1603
1860
  return;
1604
1861
  }
1605
1862
  const config = plugin.configJson;
1863
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1606
1864
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1865
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1607
1866
  if (select === 'serial')
1608
1867
  this.log.info(`Selected device serial ${data.params.serial}`);
1609
1868
  if (select === 'name')
1610
1869
  this.log.info(`Selected device name ${data.params.name}`);
1611
1870
  if (config && select && (select === 'serial' || select === 'name')) {
1871
+ // Remove postfix from the serial if it exists
1612
1872
  if (config.postfix) {
1613
1873
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1614
1874
  }
1875
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1615
1876
  if (isValidArray(config.whiteList, 1)) {
1616
1877
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1617
1878
  config.whiteList.push(data.params.serial);
@@ -1620,6 +1881,7 @@ export class Frontend extends EventEmitter {
1620
1881
  config.whiteList.push(data.params.name);
1621
1882
  }
1622
1883
  }
1884
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1623
1885
  if (isValidArray(config.blackList, 1)) {
1624
1886
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1625
1887
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1647,7 +1909,9 @@ export class Frontend extends EventEmitter {
1647
1909
  return;
1648
1910
  }
1649
1911
  const config = plugin.configJson;
1912
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1650
1913
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1914
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1651
1915
  if (select === 'serial')
1652
1916
  this.log.info(`Unselected device serial ${data.params.serial}`);
1653
1917
  if (select === 'name')
@@ -1656,6 +1920,7 @@ export class Frontend extends EventEmitter {
1656
1920
  if (config.postfix) {
1657
1921
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1658
1922
  }
1923
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1659
1924
  if (isValidArray(config.whiteList, 1)) {
1660
1925
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1661
1926
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1664,6 +1929,7 @@ export class Frontend extends EventEmitter {
1664
1929
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1665
1930
  }
1666
1931
  }
1932
+ // Add the serial to the blackList
1667
1933
  if (isValidArray(config.blackList)) {
1668
1934
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1669
1935
  config.blackList.push(data.params.serial);
@@ -1686,6 +1952,7 @@ export class Frontend extends EventEmitter {
1686
1952
  }
1687
1953
  }
1688
1954
  else {
1955
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1689
1956
  const localData = data;
1690
1957
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1691
1958
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1695,83 +1962,206 @@ export class Frontend extends EventEmitter {
1695
1962
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1696
1963
  }
1697
1964
  }
1965
+ /**
1966
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1967
+ *
1968
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1969
+ * @param {string} time - The time string of the message
1970
+ * @param {string} name - The logger name of the message
1971
+ * @param {string} message - The content of the message.
1972
+ *
1973
+ * @remarks
1974
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1975
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1976
+ * The function sends the message to all connected clients.
1977
+ */
1698
1978
  wssSendLogMessage(level, time, name, message) {
1699
1979
  if (!level || !time || !name || !message)
1700
1980
  return;
1981
+ // Remove ANSI escape codes from the message
1982
+ // eslint-disable-next-line no-control-regex
1701
1983
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1984
+ // Remove leading asterisks from the message
1702
1985
  message = message.replace(/^\*+/, '');
1986
+ // Replace all occurrences of \t and \n
1703
1987
  message = message.replace(/[\t\n]/g, '');
1988
+ // Remove non-printable characters
1989
+ // eslint-disable-next-line no-control-regex
1704
1990
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1991
+ // Replace all occurrences of \" with "
1705
1992
  message = message.replace(/\\"/g, '"');
1993
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1706
1994
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1995
+ // Define the maximum allowed length for continuous characters without a space
1707
1996
  const maxContinuousLength = 100;
1708
1997
  const keepStartLength = 20;
1709
1998
  const keepEndLength = 20;
1999
+ // Split the message into words
1710
2000
  message = message
1711
2001
  .split(' ')
1712
2002
  .map((word) => {
2003
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1713
2004
  if (word.length > maxContinuousLength) {
1714
2005
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1715
2006
  }
1716
2007
  return word;
1717
2008
  })
1718
2009
  .join(' ');
1719
- this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', params: { level, time, name, message } });
2010
+ // Send the message to all connected clients
2011
+ this.wssBroadcastMessage({ id: 0 /* WsBroadcastMessageId.Log */, src: 'Matterbridge', dst: 'Frontend', method: 'log', params: { level, time, name, message } });
1720
2012
  }
2013
+ /**
2014
+ * Sends a need to refresh WebSocket message to all connected clients.
2015
+ *
2016
+ * @param {string} changed - The changed value.
2017
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2018
+ * possible values for changed:
2019
+ * - 'settings'
2020
+ * - 'plugins'
2021
+ * - 'devices'
2022
+ * - 'matter' with param 'matter' (QRDiv component)
2023
+ * @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
2024
+ */
1721
2025
  wssSendRefreshRequired(changed, params) {
1722
2026
  this.log.debug('Sending a refresh required message to all connected clients');
1723
- this.wssBroadcastMessage({ id: 1, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed, ...params } });
2027
+ // Send the message to all connected clients
2028
+ this.wssBroadcastMessage({ id: 1 /* WsBroadcastMessageId.RefreshRequired */, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed, ...params } });
1724
2029
  }
2030
+ /**
2031
+ * Sends a need to restart WebSocket message to all connected clients.
2032
+ *
2033
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2034
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2035
+ */
1725
2036
  wssSendRestartRequired(snackbar = true, fixed = false) {
1726
2037
  this.log.debug('Sending a restart required message to all connected clients');
1727
2038
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1728
2039
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1729
2040
  if (snackbar === true)
1730
2041
  this.wssSendSnackbarMessage(`Restart required`, 0);
1731
- this.wssBroadcastMessage({ id: 2, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } });
2042
+ // Send the message to all connected clients
2043
+ this.wssBroadcastMessage({ id: 2 /* WsBroadcastMessageId.RestartRequired */, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } });
1732
2044
  }
2045
+ /**
2046
+ * Sends a no need to restart WebSocket message to all connected clients.
2047
+ *
2048
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2049
+ */
1733
2050
  wssSendRestartNotRequired(snackbar = true) {
1734
2051
  this.log.debug('Sending a restart not required message to all connected clients');
1735
2052
  this.matterbridge.matterbridgeInformation.restartRequired = false;
1736
2053
  if (snackbar === true)
1737
2054
  this.wssSendCloseSnackbarMessage(`Restart required`);
1738
- this.wssBroadcastMessage({ id: 3, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required' });
2055
+ // Send the message to all connected clients
2056
+ this.wssBroadcastMessage({ id: 3 /* WsBroadcastMessageId.RestartNotRequired */, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required' });
1739
2057
  }
2058
+ /**
2059
+ * Sends a need to update WebSocket message to all connected clients.
2060
+ *
2061
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2062
+ */
1740
2063
  wssSendUpdateRequired(devVersion = false) {
1741
2064
  this.log.debug('Sending an update required message to all connected clients');
1742
2065
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1743
- this.wssBroadcastMessage({ id: 8, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required' });
2066
+ // Send the message to all connected clients
2067
+ this.wssBroadcastMessage({ id: 8 /* WsBroadcastMessageId.UpdateRequired */, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required' });
1744
2068
  }
2069
+ /**
2070
+ * Sends a cpu update message to all connected clients.
2071
+ *
2072
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2073
+ */
1745
2074
  wssSendCpuUpdate(cpuUsage) {
1746
2075
  if (hasParameter('debug'))
1747
2076
  this.log.debug('Sending a cpu update message to all connected clients');
1748
- this.wssBroadcastMessage({ id: 4, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
2077
+ // Send the message to all connected clients
2078
+ this.wssBroadcastMessage({ id: 4 /* WsBroadcastMessageId.CpuUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
1749
2079
  }
2080
+ /**
2081
+ * Sends a memory update message to all connected clients.
2082
+ *
2083
+ * @param {string} totalMemory - The total memory in bytes.
2084
+ * @param {string} freeMemory - The free memory in bytes.
2085
+ * @param {string} rss - The resident set size in bytes.
2086
+ * @param {string} heapTotal - The total heap memory in bytes.
2087
+ * @param {string} heapUsed - The used heap memory in bytes.
2088
+ * @param {string} external - The external memory in bytes.
2089
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2090
+ */
1750
2091
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1751
2092
  if (hasParameter('debug'))
1752
2093
  this.log.debug('Sending a memory update message to all connected clients');
1753
- this.wssBroadcastMessage({ id: 5, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2094
+ // Send the message to all connected clients
2095
+ this.wssBroadcastMessage({ id: 5 /* WsBroadcastMessageId.MemoryUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1754
2096
  }
2097
+ /**
2098
+ * Sends an uptime update message to all connected clients.
2099
+ *
2100
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2101
+ * @param {string} processUptime - The process uptime in a human-readable format.
2102
+ */
1755
2103
  wssSendUptimeUpdate(systemUptime, processUptime) {
1756
2104
  if (hasParameter('debug'))
1757
2105
  this.log.debug('Sending a uptime update message to all connected clients');
1758
- this.wssBroadcastMessage({ id: 6, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } });
2106
+ // Send the message to all connected clients
2107
+ this.wssBroadcastMessage({ id: 6 /* WsBroadcastMessageId.UptimeUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } });
1759
2108
  }
2109
+ /**
2110
+ * Sends an open snackbar message to all connected clients.
2111
+ *
2112
+ * @param {string} message - The message to send.
2113
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2114
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2115
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2116
+ *
2117
+ * @remarks
2118
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2119
+ */
1760
2120
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1761
2121
  this.log.debug('Sending a snackbar message to all connected clients');
1762
- this.wssBroadcastMessage({ id: 7, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', params: { message, timeout, severity } });
2122
+ // Send the message to all connected clients
2123
+ this.wssBroadcastMessage({ id: 7 /* WsBroadcastMessageId.Snackbar */, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', params: { message, timeout, severity } });
1763
2124
  }
2125
+ /**
2126
+ * Sends a close snackbar message to all connected clients.
2127
+ * It will close the snackbar message with the same message and timeout = 0.
2128
+ *
2129
+ * @param {string} message - The message to send.
2130
+ */
1764
2131
  wssSendCloseSnackbarMessage(message) {
1765
2132
  this.log.debug('Sending a close snackbar message to all connected clients');
1766
- this.wssBroadcastMessage({ id: 10, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', params: { message } });
2133
+ // Send the message to all connected clients
2134
+ this.wssBroadcastMessage({ id: 10 /* WsBroadcastMessageId.CloseSnackbar */, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', params: { message } });
1767
2135
  }
2136
+ /**
2137
+ * Sends an attribute update message to all connected WebSocket clients.
2138
+ *
2139
+ * @param {string | undefined} plugin - The name of the plugin.
2140
+ * @param {string | undefined} serialNumber - The serial number of the device.
2141
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2142
+ * @param {string} cluster - The cluster name where the attribute belongs.
2143
+ * @param {string} attribute - The name of the attribute that changed.
2144
+ * @param {number | string | boolean} value - The new value of the attribute.
2145
+ *
2146
+ * @remarks
2147
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2148
+ * with the updated attribute information.
2149
+ */
1768
2150
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1769
2151
  this.log.debug('Sending an attribute update message to all connected clients');
1770
- this.wssBroadcastMessage({ id: 9, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } });
2152
+ // Send the message to all connected clients
2153
+ this.wssBroadcastMessage({ id: 9 /* WsBroadcastMessageId.StateUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } });
1771
2154
  }
2155
+ /**
2156
+ * Sends a message to all connected clients.
2157
+ * This is an helper function to send a broadcast message to all connected clients.
2158
+ *
2159
+ * @param {WsMessageBroadcast} msg - The message to send.
2160
+ */
1772
2161
  wssBroadcastMessage(msg) {
2162
+ // Send the message to all connected clients
1773
2163
  const stringifiedMsg = JSON.stringify(msg);
1774
- if (msg.id !== 0)
2164
+ if (msg.id !== 0 /* WsBroadcastMessageId.Log */)
1775
2165
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
1776
2166
  this.webSocketServer?.clients.forEach((client) => {
1777
2167
  if (client.readyState === WebSocket.OPEN) {
@@ -1780,3 +2170,4 @@ export class Frontend extends EventEmitter {
1780
2170
  });
1781
2171
  }
1782
2172
  }
2173
+ //# sourceMappingURL=frontend.js.map