matterbridge 3.3.0-dev-20251003-626ea2f → 3.3.0

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 (282) 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 +232 -0
  122. package/dist/frontend.d.ts.map +1 -0
  123. package/dist/frontend.js +454 -61
  124. package/dist/frontend.js.map +1 -0
  125. package/dist/frontendTypes.d.ts +514 -0
  126. package/dist/frontendTypes.d.ts.map +1 -0
  127. package/dist/frontendTypes.js +45 -0
  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 +430 -0
  170. package/dist/matterbridge.d.ts.map +1 -0
  171. package/dist/matterbridge.js +788 -68
  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 +1534 -0
  190. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  191. package/dist/matterbridgeEndpoint.js +1398 -58
  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 +402 -0
  198. package/dist/matterbridgePlatform.d.ts.map +1 -0
  199. package/dist/matterbridgePlatform.js +340 -1
  200. package/dist/matterbridgePlatform.js.map +1 -0
  201. package/dist/matterbridgeTypes.d.ts +201 -0
  202. package/dist/matterbridgeTypes.d.ts.map +1 -0
  203. package/dist/matterbridgeTypes.js +30 -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 +168 -7
  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 +34 -0
  274. package/dist/utils/spawn.d.ts.map +1 -0
  275. package/dist/utils/spawn.js +69 -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/npm-shrinkwrap.json +2 -2
  282. 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,16 +29,20 @@ 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
- import { plg } from './matterbridgeTypes.js';
45
+ import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from './matterbridgeTypes.js';
18
46
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
19
47
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
20
48
  export class Frontend extends EventEmitter {
@@ -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),
@@ -304,56 +381,64 @@ export class Frontend extends EventEmitter {
304
381
  space_available_size: this.formatMemoryUsage(space.space_available_size),
305
382
  physical_space_size: this.formatMemoryUsage(space.physical_space_size),
306
383
  }));
307
- const { default: module } = await import('node:module');
308
- const loadedModules = module._cache ? Object.keys(module._cache).sort() : [];
384
+ const { createRequire } = await import('node:module');
385
+ const require = createRequire(import.meta.url);
386
+ const cjsModules = Object.keys(require.cache).sort();
309
387
  const memoryReport = {
310
388
  memoryUsage,
311
389
  heapStats,
312
390
  heapSpaces,
313
- loadedModules,
391
+ cjsModules,
314
392
  };
315
393
  res.status(200).json(memoryReport);
316
394
  });
395
+ // Endpoint to provide settings
317
396
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
318
397
  this.log.debug('The frontend sent /api/settings');
319
398
  res.json(await this.getApiSettings());
320
399
  });
400
+ // Endpoint to provide plugins
321
401
  this.expressApp.get('/api/plugins', async (req, res) => {
322
402
  this.log.debug('The frontend sent /api/plugins');
323
403
  res.json(this.getPlugins());
324
404
  });
405
+ // Endpoint to provide devices
325
406
  this.expressApp.get('/api/devices', async (req, res) => {
326
407
  this.log.debug('The frontend sent /api/devices');
327
- const devices = await this.getDevices();
408
+ const devices = this.getDevices();
328
409
  res.json(devices);
329
410
  });
411
+ // Endpoint to view the matterbridge log
330
412
  this.expressApp.get('/api/view-mblog', async (req, res) => {
331
413
  this.log.debug('The frontend sent /api/view-mblog');
332
414
  try {
333
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
415
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
334
416
  res.type('text/plain');
335
417
  res.send(data);
336
418
  }
337
419
  catch (error) {
338
- this.log.error(`Error reading matterbridge log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
420
+ this.log.error(`Error reading matterbridge log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
339
421
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
340
422
  }
341
423
  });
424
+ // Endpoint to view the matter.js log
342
425
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
343
426
  this.log.debug('The frontend sent /api/view-mjlog');
344
427
  try {
345
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
428
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
346
429
  res.type('text/plain');
347
430
  res.send(data);
348
431
  }
349
432
  catch (error) {
350
- this.log.error(`Error reading matter log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
433
+ this.log.error(`Error reading matter log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
351
434
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
352
435
  }
353
436
  });
437
+ // Endpoint to view the diagnostic.log
354
438
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
355
439
  this.log.debug('The frontend sent /api/view-diagnostic');
356
440
  const serverNodes = [];
441
+ // istanbul ignore else
357
442
  if (this.matterbridge.bridgeMode === 'bridge') {
358
443
  if (this.matterbridge.serverNode)
359
444
  serverNodes.push(this.matterbridge.serverNode);
@@ -364,6 +449,7 @@ export class Frontend extends EventEmitter {
364
449
  serverNodes.push(plugin.serverNode);
365
450
  }
366
451
  }
452
+ // istanbul ignore next
367
453
  for (const device of this.matterbridge.devices.array()) {
368
454
  if (device.serverNode)
369
455
  serverNodes.push(device.serverNode);
@@ -386,17 +472,20 @@ export class Frontend extends EventEmitter {
386
472
  values: [...serverNodes],
387
473
  })));
388
474
  delete Logger.destinations.diagnostic;
389
- await wait(500);
475
+ await wait(500); // Wait for the log to be written
390
476
  try {
391
477
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
392
478
  res.type('text/plain');
393
479
  res.send(data.slice(29));
394
480
  }
395
481
  catch (error) {
396
- this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
482
+ // istanbul ignore next
483
+ this.log.error(`Error reading diagnostic log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
484
+ // istanbul ignore next
397
485
  res.status(500).send('Error reading diagnostic log file.');
398
486
  }
399
487
  });
488
+ // Endpoint to view the shelly log
400
489
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
401
490
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
402
491
  try {
@@ -405,48 +494,53 @@ export class Frontend extends EventEmitter {
405
494
  res.send(data);
406
495
  }
407
496
  catch (error) {
408
- this.log.error(`Error reading shelly log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
497
+ this.log.error(`Error reading shelly log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
409
498
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
410
499
  }
411
500
  });
501
+ // Endpoint to download the matterbridge log
412
502
  this.expressApp.get('/api/download-mblog', async (req, res) => {
413
- this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
503
+ this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
414
504
  try {
415
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
416
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
417
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
505
+ await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
506
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
507
+ await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
418
508
  }
419
509
  catch (error) {
420
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
510
+ await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
421
511
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
422
512
  }
423
513
  res.type('text/plain');
424
- res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
514
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
515
+ /* istanbul ignore if */
425
516
  if (error) {
426
- this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
517
+ this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
427
518
  res.status(500).send('Error downloading the matterbridge log file');
428
519
  }
429
520
  });
430
521
  });
522
+ // Endpoint to download the matter log
431
523
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
432
- this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
524
+ this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
433
525
  try {
434
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
435
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
436
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
526
+ await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
527
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
528
+ await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
437
529
  }
438
530
  catch (error) {
439
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
531
+ await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
440
532
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
441
533
  }
442
534
  res.type('text/plain');
443
- res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
535
+ res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
536
+ /* istanbul ignore if */
444
537
  if (error) {
445
- this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
538
+ this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
446
539
  res.status(500).send('Error downloading the matter log file');
447
540
  }
448
541
  });
449
542
  });
543
+ // Endpoint to download the shelly log
450
544
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
451
545
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
452
546
  try {
@@ -460,74 +554,90 @@ export class Frontend extends EventEmitter {
460
554
  }
461
555
  res.type('text/plain');
462
556
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
557
+ /* istanbul ignore if */
463
558
  if (error) {
464
559
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
465
560
  res.status(500).send('Error downloading Shelly system log file');
466
561
  }
467
562
  });
468
563
  });
564
+ // Endpoint to download the matterbridge storage directory
469
565
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
470
566
  this.log.debug('The frontend sent /api/download-mbstorage');
471
- await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
472
- res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
567
+ await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
568
+ res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
569
+ /* istanbul ignore if */
473
570
  if (error) {
474
- this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
571
+ this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
475
572
  res.status(500).send('Error downloading the matterbridge storage file');
476
573
  }
477
574
  });
478
575
  });
576
+ // Endpoint to download the matter storage file
479
577
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
480
578
  this.log.debug('The frontend sent /api/download-mjstorage');
481
- await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
482
- res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
579
+ await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
580
+ res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
581
+ /* istanbul ignore if */
483
582
  if (error) {
484
- this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
583
+ this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
485
584
  res.status(500).send('Error downloading the matter storage zip file');
486
585
  }
487
586
  });
488
587
  });
588
+ // Endpoint to download the matterbridge plugin directory
489
589
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
490
590
  this.log.debug('The frontend sent /api/download-pluginstorage');
491
591
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
492
592
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
593
+ /* istanbul ignore if */
493
594
  if (error) {
494
595
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
495
596
  res.status(500).send('Error downloading the matterbridge plugin storage file');
496
597
  }
497
598
  });
498
599
  });
600
+ // Endpoint to download the matterbridge plugin config files
499
601
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
500
602
  this.log.debug('The frontend sent /api/download-pluginconfig');
501
603
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
502
604
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
605
+ /* istanbul ignore if */
503
606
  if (error) {
504
607
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
505
608
  res.status(500).send('Error downloading the matterbridge plugin config file');
506
609
  }
507
610
  });
508
611
  });
612
+ // Endpoint to download the matterbridge backup (created with the backup command)
509
613
  this.expressApp.get('/api/download-backup', async (req, res) => {
510
614
  this.log.debug('The frontend sent /api/download-backup');
511
615
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
616
+ /* istanbul ignore if */
512
617
  if (error) {
513
618
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
514
619
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
515
620
  }
516
621
  });
517
622
  });
623
+ // Endpoint to upload a package
518
624
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
519
625
  const { filename } = req.body;
520
626
  const file = req.file;
627
+ /* istanbul ignore if */
521
628
  if (!file || !filename) {
522
629
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
523
630
  res.status(400).send('Invalid request: file and filename are required');
524
631
  return;
525
632
  }
526
633
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
634
+ // Define the path where the plugin file will be saved
527
635
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
528
636
  try {
637
+ // Move the uploaded file to the specified path
529
638
  await fs.rename(file.path, filePath);
530
639
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
640
+ // Install the plugin package
531
641
  if (filename.endsWith('.tgz')) {
532
642
  const { spawnCommand } = await import('./utils/spawn.js');
533
643
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
@@ -547,6 +657,7 @@ export class Frontend extends EventEmitter {
547
657
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
548
658
  }
549
659
  });
660
+ // Fallback for routing (must be the last route)
550
661
  this.expressApp.use((req, res) => {
551
662
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
552
663
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -555,13 +666,16 @@ export class Frontend extends EventEmitter {
555
666
  }
556
667
  async stop() {
557
668
  this.log.debug('Stopping the frontend...');
669
+ // Remove listeners from the express app
558
670
  if (this.expressApp) {
559
671
  this.expressApp.removeAllListeners();
560
672
  this.expressApp = undefined;
561
673
  this.log.debug('Frontend app closed successfully');
562
674
  }
675
+ // Close the WebSocket server
563
676
  if (this.webSocketServer) {
564
677
  this.log.debug('Closing WebSocket server...');
678
+ // Close all active connections
565
679
  this.webSocketServer.clients.forEach((client) => {
566
680
  if (client.readyState === WebSocket.OPEN) {
567
681
  client.close();
@@ -570,6 +684,7 @@ export class Frontend extends EventEmitter {
570
684
  await withTimeout(new Promise((resolve) => {
571
685
  this.webSocketServer?.close((error) => {
572
686
  if (error) {
687
+ // istanbul ignore next
573
688
  this.log.error(`Error closing WebSocket server: ${error}`);
574
689
  }
575
690
  else {
@@ -582,8 +697,27 @@ export class Frontend extends EventEmitter {
582
697
  this.webSocketServer.removeAllListeners();
583
698
  this.webSocketServer = undefined;
584
699
  }
700
+ // Close the http server
585
701
  if (this.httpServer) {
586
702
  this.log.debug('Closing http server...');
703
+ /*
704
+ await withTimeout(
705
+ new Promise<void>((resolve) => {
706
+ this.httpServer?.close((error) => {
707
+ if (error) {
708
+ // istanbul ignore next
709
+ this.log.error(`Error closing http server: ${error}`);
710
+ } else {
711
+ this.log.debug('Http server closed successfully');
712
+ this.emit('server_stopped');
713
+ }
714
+ resolve();
715
+ });
716
+ }),
717
+ 5000,
718
+ false,
719
+ );
720
+ */
587
721
  this.httpServer.close();
588
722
  this.log.debug('Http server closed successfully');
589
723
  this.listening = false;
@@ -592,8 +726,27 @@ export class Frontend extends EventEmitter {
592
726
  this.httpServer = undefined;
593
727
  this.log.debug('Frontend http server closed successfully');
594
728
  }
729
+ // Close the https server
595
730
  if (this.httpsServer) {
596
731
  this.log.debug('Closing https server...');
732
+ /*
733
+ await withTimeout(
734
+ new Promise<void>((resolve) => {
735
+ this.httpsServer?.close((error) => {
736
+ if (error) {
737
+ // istanbul ignore next
738
+ this.log.error(`Error closing https server: ${error}`);
739
+ } else {
740
+ this.log.debug('Https server closed successfully');
741
+ this.emit('server_stopped');
742
+ }
743
+ resolve();
744
+ });
745
+ }),
746
+ 5000,
747
+ false,
748
+ );
749
+ */
597
750
  this.httpsServer.close();
598
751
  this.log.debug('Https server closed successfully');
599
752
  this.listening = false;
@@ -604,6 +757,7 @@ export class Frontend extends EventEmitter {
604
757
  }
605
758
  this.log.debug('Frontend stopped successfully');
606
759
  }
760
+ // Function to format bytes to KB, MB, or GB
607
761
  formatMemoryUsage = (bytes) => {
608
762
  if (bytes >= 1024 ** 3) {
609
763
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -615,6 +769,7 @@ export class Frontend extends EventEmitter {
615
769
  return `${(bytes / 1024).toFixed(2)} KB`;
616
770
  }
617
771
  };
772
+ // Function to format system uptime with only the most significant unit
618
773
  formatOsUpTime = (seconds) => {
619
774
  if (seconds >= 86400) {
620
775
  const days = Math.floor(seconds / 86400);
@@ -630,7 +785,13 @@ export class Frontend extends EventEmitter {
630
785
  }
631
786
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
632
787
  };
788
+ /**
789
+ * Retrieves the api settings data.
790
+ *
791
+ * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
792
+ */
633
793
  async getApiSettings() {
794
+ // Update the system information
634
795
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
635
796
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
636
797
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -639,6 +800,7 @@ export class Frontend extends EventEmitter {
639
800
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
640
801
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
641
802
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
803
+ // Update the matterbridge information
642
804
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
643
805
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
644
806
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
@@ -652,6 +814,12 @@ export class Frontend extends EventEmitter {
652
814
  this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
653
815
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
654
816
  }
817
+ /**
818
+ * Retrieves the reachable attribute.
819
+ *
820
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
821
+ * @returns {boolean} The reachable attribute.
822
+ */
655
823
  getReachability(device) {
656
824
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
657
825
  return false;
@@ -663,6 +831,12 @@ export class Frontend extends EventEmitter {
663
831
  return true;
664
832
  return false;
665
833
  }
834
+ /**
835
+ * Retrieves the power source attribute.
836
+ *
837
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
838
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
839
+ */
666
840
  getPowerSource(endpoint) {
667
841
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
668
842
  return undefined;
@@ -678,13 +852,22 @@ export class Frontend extends EventEmitter {
678
852
  }
679
853
  return;
680
854
  };
855
+ // Root endpoint
681
856
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
682
857
  return powerSource(endpoint);
858
+ // Child endpoints
683
859
  for (const child of endpoint.getChildEndpoints()) {
684
860
  if (child.hasClusterServer(PowerSource.Cluster.id))
685
861
  return powerSource(child);
686
862
  }
687
863
  }
864
+ /**
865
+ * Retrieves the cluster text description from a given device.
866
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
867
+ *
868
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
869
+ * @returns {string} The attributes description of the cluster servers in the device.
870
+ */
688
871
  getClusterTextFromDevice(device) {
689
872
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
690
873
  return '';
@@ -695,6 +878,7 @@ export class Frontend extends EventEmitter {
695
878
  if (composed)
696
879
  return 'Composed: ' + composed.value;
697
880
  }
881
+ // istanbul ignore next cause is not reachable
698
882
  return '';
699
883
  };
700
884
  const getFixedLabel = (device) => {
@@ -704,11 +888,13 @@ export class Frontend extends EventEmitter {
704
888
  if (composed)
705
889
  return 'Composed: ' + composed.value;
706
890
  }
891
+ // istanbul ignore next cause is not reacheable
707
892
  return '';
708
893
  };
709
894
  let attributes = '';
710
895
  let supportedModes = [];
711
896
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
897
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
712
898
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
713
899
  return;
714
900
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -798,11 +984,17 @@ export class Frontend extends EventEmitter {
798
984
  if (clusterName === 'userLabel' && attributeName === 'labelList')
799
985
  attributes += `${getUserLabel(device)} `;
800
986
  });
987
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
801
988
  return attributes.trimStart().trimEnd();
802
989
  }
990
+ /**
991
+ * Retrieves the registered plugins sanitized for res.json().
992
+ *
993
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
994
+ */
803
995
  getPlugins() {
804
996
  if (this.matterbridge.hasCleanupStarted)
805
- return [];
997
+ return []; // Skip if cleanup has started
806
998
  const baseRegisteredPlugins = [];
807
999
  for (const plugin of this.matterbridge.plugins) {
808
1000
  baseRegisteredPlugins.push({
@@ -830,18 +1022,27 @@ export class Frontend extends EventEmitter {
830
1022
  schemaJson: plugin.schemaJson,
831
1023
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
832
1024
  hasBlackList: plugin.configJson?.blackList !== undefined,
1025
+ // Childbridge mode specific data
833
1026
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
834
1027
  });
835
1028
  }
836
1029
  return baseRegisteredPlugins;
837
1030
  }
838
- async getDevices(pluginName) {
1031
+ /**
1032
+ * Retrieves the devices from Matterbridge.
1033
+ *
1034
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1035
+ * @returns {ApiDevices[]} An array of ApiDevices for the frontend.
1036
+ */
1037
+ getDevices(pluginName) {
839
1038
  if (this.matterbridge.hasCleanupStarted)
840
- return [];
1039
+ return []; // Skip if cleanup has started
841
1040
  const devices = [];
842
1041
  for (const device of this.matterbridge.devices.array()) {
1042
+ // Filter by pluginName if provided
843
1043
  if (pluginName && pluginName !== device.plugin)
844
1044
  continue;
1045
+ // Check if the device has the required properties
845
1046
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
846
1047
  continue;
847
1048
  devices.push({
@@ -861,24 +1062,39 @@ export class Frontend extends EventEmitter {
861
1062
  }
862
1063
  return devices;
863
1064
  }
1065
+ /**
1066
+ * Retrieves the clusters from a given plugin and endpoint number.
1067
+ *
1068
+ * Response for /api/clusters
1069
+ *
1070
+ * @param {string} pluginName - The name of the plugin.
1071
+ * @param {number} endpointNumber - The endpoint number.
1072
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1073
+ */
864
1074
  getClusters(pluginName, endpointNumber) {
865
1075
  if (this.matterbridge.hasCleanupStarted)
866
- return;
1076
+ return; // Skip if cleanup has started
867
1077
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
868
1078
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
869
1079
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
870
1080
  return;
871
1081
  }
1082
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1083
+ // Get the device types from the main endpoint
872
1084
  const deviceTypes = [];
873
1085
  const clusters = [];
874
1086
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
875
1087
  deviceTypes.push(d.deviceType);
876
1088
  });
1089
+ // Get the clusters from the main endpoint
877
1090
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
878
1091
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
879
1092
  return;
880
1093
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
881
1094
  return;
1095
+ // console.log(
1096
+ // `${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}`,
1097
+ // );
882
1098
  clusters.push({
883
1099
  endpoint: endpoint.number.toString(),
884
1100
  number: endpoint.number,
@@ -892,12 +1108,19 @@ export class Frontend extends EventEmitter {
892
1108
  attributeLocalValue: attributeValue,
893
1109
  });
894
1110
  });
1111
+ // Get the child endpoints
895
1112
  const childEndpoints = endpoint.getChildEndpoints();
1113
+ // if (childEndpoints.length === 0) {
1114
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1115
+ // }
896
1116
  childEndpoints.forEach((childEndpoint) => {
1117
+ // istanbul ignore if cause is not reachable: should never happen but ...
897
1118
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
898
1119
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
899
1120
  return;
900
1121
  }
1122
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1123
+ // Get the device types of the child endpoint
901
1124
  const deviceTypes = [];
902
1125
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
903
1126
  deviceTypes.push(d.deviceType);
@@ -907,6 +1130,9 @@ export class Frontend extends EventEmitter {
907
1130
  return;
908
1131
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
909
1132
  return;
1133
+ // console.log(
1134
+ // `${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}`,
1135
+ // );
910
1136
  clusters.push({
911
1137
  endpoint: childEndpoint.number.toString(),
912
1138
  number: childEndpoint.number,
@@ -923,6 +1149,13 @@ export class Frontend extends EventEmitter {
923
1149
  });
924
1150
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
925
1151
  }
1152
+ /**
1153
+ * Handles incoming websocket api request messages from the Matterbridge frontend.
1154
+ *
1155
+ * @param {WebSocket} client - The websocket client that sent the message.
1156
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1157
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1158
+ */
926
1159
  async wsMessageHandler(client, message) {
927
1160
  let data;
928
1161
  const sendResponse = (data) => {
@@ -942,7 +1175,7 @@ export class Frontend extends EventEmitter {
942
1175
  };
943
1176
  try {
944
1177
  data = JSON.parse(message.toString());
945
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1178
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
946
1179
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
947
1180
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
948
1181
  return;
@@ -985,36 +1218,49 @@ export class Frontend extends EventEmitter {
985
1218
  this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
986
1219
  const packageName = localData.params.packageName.replace(/@.*$/, '');
987
1220
  if (localData.params.restart === false && packageName !== 'matterbridge') {
1221
+ // The install comes from InstallPlugins
988
1222
  this.matterbridge.plugins
989
1223
  .add(packageName)
990
1224
  .then((plugin) => {
1225
+ // istanbul ignore next if
991
1226
  if (plugin) {
1227
+ // The plugin is not registered
992
1228
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1229
+ // In childbridge mode the plugins server node is not started when added
993
1230
  if (this.matterbridge.bridgeMode === 'childbridge')
994
1231
  this.wssSendRestartRequired(true, true);
995
1232
  this.matterbridge.plugins
996
1233
  .load(plugin, true, 'The plugin has been added', true)
1234
+ // eslint-disable-next-line promise/no-nesting
997
1235
  .then(() => {
998
1236
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
999
1237
  this.wssSendRefreshRequired('plugins');
1000
1238
  this.wssSendRefreshRequired('devices');
1001
1239
  return;
1002
1240
  })
1241
+ // eslint-disable-next-line promise/no-nesting
1003
1242
  .catch((_error) => {
1243
+ //
1004
1244
  });
1005
1245
  }
1006
1246
  else {
1247
+ // The plugin is already registered
1007
1248
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1008
1249
  this.wssSendRefreshRequired('plugins');
1009
1250
  this.wssSendRestartRequired(true, true);
1010
1251
  }
1011
1252
  return;
1012
1253
  })
1254
+ // eslint-disable-next-line promise/no-nesting
1013
1255
  .catch((_error) => {
1256
+ //
1014
1257
  });
1015
1258
  }
1016
1259
  else {
1260
+ // The package is matterbridge
1261
+ // istanbul ignore next
1017
1262
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1263
+ // istanbul ignore next if
1018
1264
  if (this.matterbridge.restartMode !== '') {
1019
1265
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1020
1266
  this.matterbridge.shutdownProcess();
@@ -1037,7 +1283,9 @@ export class Frontend extends EventEmitter {
1037
1283
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
1038
1284
  return;
1039
1285
  }
1286
+ // The package is a plugin
1040
1287
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1288
+ // istanbul ignore next if
1041
1289
  if (plugin) {
1042
1290
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1043
1291
  await this.matterbridge.plugins.remove(data.params.packageName);
@@ -1045,6 +1293,7 @@ export class Frontend extends EventEmitter {
1045
1293
  this.wssSendRefreshRequired('plugins');
1046
1294
  this.wssSendRefreshRequired('devices');
1047
1295
  }
1296
+ // Uninstall the package
1048
1297
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1049
1298
  const { spawnCommand } = await import('./utils/spawn.js');
1050
1299
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
@@ -1087,6 +1336,7 @@ export class Frontend extends EventEmitter {
1087
1336
  return;
1088
1337
  })
1089
1338
  .catch((_error) => {
1339
+ //
1090
1340
  });
1091
1341
  }
1092
1342
  else {
@@ -1134,6 +1384,7 @@ export class Frontend extends EventEmitter {
1134
1384
  return;
1135
1385
  })
1136
1386
  .catch((_error) => {
1387
+ //
1137
1388
  });
1138
1389
  }
1139
1390
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1159,6 +1410,7 @@ export class Frontend extends EventEmitter {
1159
1410
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1160
1411
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1161
1412
  if (plugin.serverNode) {
1413
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1162
1414
  await this.matterbridge.stopServerNode(plugin.serverNode);
1163
1415
  plugin.serverNode = undefined;
1164
1416
  }
@@ -1168,18 +1420,20 @@ export class Frontend extends EventEmitter {
1168
1420
  this.matterbridge.devices.remove(device);
1169
1421
  }
1170
1422
  }
1423
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1171
1424
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1172
1425
  await this.matterbridge.createDynamicPlugin(plugin);
1173
1426
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1174
- plugin.restartRequired = false;
1427
+ plugin.restartRequired = false; // Reset plugin restartRequired
1175
1428
  let needRestart = 0;
1176
1429
  for (const plugin of this.matterbridge.plugins) {
1177
1430
  if (plugin.restartRequired)
1178
1431
  needRestart++;
1179
1432
  }
1180
1433
  if (needRestart === 0) {
1181
- this.wssSendRestartNotRequired(true);
1434
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1182
1435
  }
1436
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1183
1437
  if (plugin.serverNode)
1184
1438
  await this.matterbridge.startServerNode(plugin.serverNode);
1185
1439
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1336,7 +1590,7 @@ export class Frontend extends EventEmitter {
1336
1590
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
1337
1591
  }
1338
1592
  else if (data.method === '/api/devices') {
1339
- const devices = await this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1593
+ const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1340
1594
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
1341
1595
  }
1342
1596
  else if (data.method === '/api/clusters') {
@@ -1436,22 +1690,22 @@ export class Frontend extends EventEmitter {
1436
1690
  if (isValidString(data.params.value, 4)) {
1437
1691
  this.log.debug('Matterbridge logger level:', data.params.value);
1438
1692
  if (data.params.value === 'Debug') {
1439
- await this.matterbridge.setLogLevel("debug");
1693
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1440
1694
  }
1441
1695
  else if (data.params.value === 'Info') {
1442
- await this.matterbridge.setLogLevel("info");
1696
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1443
1697
  }
1444
1698
  else if (data.params.value === 'Notice') {
1445
- await this.matterbridge.setLogLevel("notice");
1699
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1446
1700
  }
1447
1701
  else if (data.params.value === 'Warn') {
1448
- await this.matterbridge.setLogLevel("warn");
1702
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1449
1703
  }
1450
1704
  else if (data.params.value === 'Error') {
1451
- await this.matterbridge.setLogLevel("error");
1705
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1452
1706
  }
1453
1707
  else if (data.params.value === 'Fatal') {
1454
- await this.matterbridge.setLogLevel("fatal");
1708
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1455
1709
  }
1456
1710
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1457
1711
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1462,8 +1716,9 @@ export class Frontend extends EventEmitter {
1462
1716
  this.log.debug('Matterbridge file log:', data.params.value);
1463
1717
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1464
1718
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1719
+ // Create the file logger for matterbridge
1465
1720
  if (data.params.value)
1466
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1721
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1467
1722
  else
1468
1723
  AnsiLogger.setGlobalLogfile(undefined);
1469
1724
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1501,7 +1756,7 @@ export class Frontend extends EventEmitter {
1501
1756
  this.matterbridge.matterbridgeInformation.matterFileLogger = data.params.value;
1502
1757
  await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
1503
1758
  if (data.params.value) {
1504
- this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile);
1759
+ this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
1505
1760
  }
1506
1761
  else {
1507
1762
  this.matterbridge.matterLog.logFilePath = undefined;
@@ -1540,6 +1795,7 @@ export class Frontend extends EventEmitter {
1540
1795
  }
1541
1796
  break;
1542
1797
  case 'setmatterport':
1798
+ // eslint-disable-next-line no-case-declarations
1543
1799
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1544
1800
  if (isValidNumber(port, 5540, 5600)) {
1545
1801
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1557,6 +1813,7 @@ export class Frontend extends EventEmitter {
1557
1813
  }
1558
1814
  break;
1559
1815
  case 'setmatterdiscriminator':
1816
+ // eslint-disable-next-line no-case-declarations
1560
1817
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1561
1818
  if (isValidNumber(discriminator, 0, 4095)) {
1562
1819
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1574,6 +1831,7 @@ export class Frontend extends EventEmitter {
1574
1831
  }
1575
1832
  break;
1576
1833
  case 'setmatterpasscode':
1834
+ // eslint-disable-next-line no-case-declarations
1577
1835
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1578
1836
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1579
1837
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -1617,15 +1875,19 @@ export class Frontend extends EventEmitter {
1617
1875
  return;
1618
1876
  }
1619
1877
  const config = plugin.configJson;
1878
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1620
1879
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1880
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1621
1881
  if (select === 'serial')
1622
1882
  this.log.info(`Selected device serial ${data.params.serial}`);
1623
1883
  if (select === 'name')
1624
1884
  this.log.info(`Selected device name ${data.params.name}`);
1625
1885
  if (config && select && (select === 'serial' || select === 'name')) {
1886
+ // Remove postfix from the serial if it exists
1626
1887
  if (config.postfix) {
1627
1888
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1628
1889
  }
1890
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1629
1891
  if (isValidArray(config.whiteList, 1)) {
1630
1892
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1631
1893
  config.whiteList.push(data.params.serial);
@@ -1634,6 +1896,7 @@ export class Frontend extends EventEmitter {
1634
1896
  config.whiteList.push(data.params.name);
1635
1897
  }
1636
1898
  }
1899
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1637
1900
  if (isValidArray(config.blackList, 1)) {
1638
1901
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1639
1902
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1661,7 +1924,9 @@ export class Frontend extends EventEmitter {
1661
1924
  return;
1662
1925
  }
1663
1926
  const config = plugin.configJson;
1927
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1664
1928
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1929
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1665
1930
  if (select === 'serial')
1666
1931
  this.log.info(`Unselected device serial ${data.params.serial}`);
1667
1932
  if (select === 'name')
@@ -1670,6 +1935,7 @@ export class Frontend extends EventEmitter {
1670
1935
  if (config.postfix) {
1671
1936
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1672
1937
  }
1938
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1673
1939
  if (isValidArray(config.whiteList, 1)) {
1674
1940
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1675
1941
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1678,6 +1944,7 @@ export class Frontend extends EventEmitter {
1678
1944
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1679
1945
  }
1680
1946
  }
1947
+ // Add the serial to the blackList
1681
1948
  if (isValidArray(config.blackList)) {
1682
1949
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1683
1950
  config.blackList.push(data.params.serial);
@@ -1700,6 +1967,7 @@ export class Frontend extends EventEmitter {
1700
1967
  }
1701
1968
  }
1702
1969
  else {
1970
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1703
1971
  const localData = data;
1704
1972
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1705
1973
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1709,21 +1977,44 @@ export class Frontend extends EventEmitter {
1709
1977
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1710
1978
  }
1711
1979
  }
1980
+ /**
1981
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1982
+ *
1983
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1984
+ * @param {string} time - The time string of the message
1985
+ * @param {string} name - The logger name of the message
1986
+ * @param {string} message - The content of the message.
1987
+ *
1988
+ * @remarks
1989
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1990
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1991
+ * The function sends the message to all connected clients.
1992
+ */
1712
1993
  wssSendLogMessage(level, time, name, message) {
1713
1994
  if (!level || !time || !name || !message)
1714
1995
  return;
1996
+ // Remove ANSI escape codes from the message
1997
+ // eslint-disable-next-line no-control-regex
1715
1998
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1999
+ // Remove leading asterisks from the message
1716
2000
  message = message.replace(/^\*+/, '');
2001
+ // Replace all occurrences of \t and \n
1717
2002
  message = message.replace(/[\t\n]/g, '');
2003
+ // Remove non-printable characters
2004
+ // eslint-disable-next-line no-control-regex
1718
2005
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2006
+ // Replace all occurrences of \" with "
1719
2007
  message = message.replace(/\\"/g, '"');
2008
+ // Define the maximum allowed length for continuous characters without a space
1720
2009
  const maxContinuousLength = 100;
1721
2010
  const keepStartLength = 20;
1722
2011
  const keepEndLength = 20;
2012
+ // Split the message into words
1723
2013
  if (level !== 'spawn') {
1724
2014
  message = message
1725
2015
  .split(' ')
1726
2016
  .map((word) => {
2017
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1727
2018
  if (word.length > maxContinuousLength) {
1728
2019
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1729
2020
  }
@@ -1731,60 +2022,161 @@ export class Frontend extends EventEmitter {
1731
2022
  })
1732
2023
  .join(' ');
1733
2024
  }
2025
+ // Send the message to all connected clients
1734
2026
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
1735
2027
  }
2028
+ /**
2029
+ * Sends a need to refresh WebSocket message to all connected clients.
2030
+ *
2031
+ * @param {string} changed - The changed value.
2032
+ * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2033
+ * possible values for changed:
2034
+ * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2035
+ * - 'plugins'
2036
+ * - 'devices'
2037
+ * - 'matter' with param 'matter' (QRDiv component)
2038
+ * @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
2039
+ */
1736
2040
  wssSendRefreshRequired(changed, params) {
1737
2041
  this.log.debug('Sending a refresh required message to all connected clients');
2042
+ // Send the message to all connected clients
1738
2043
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
1739
2044
  }
2045
+ /**
2046
+ * Sends a need to restart WebSocket message to all connected clients.
2047
+ *
2048
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2049
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2050
+ */
1740
2051
  wssSendRestartRequired(snackbar = true, fixed = false) {
1741
2052
  this.log.debug('Sending a restart required message to all connected clients');
1742
2053
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1743
2054
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1744
2055
  if (snackbar === true)
1745
2056
  this.wssSendSnackbarMessage(`Restart required`, 0);
2057
+ // Send the message to all connected clients
1746
2058
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
1747
2059
  }
2060
+ /**
2061
+ * Sends a no need to restart WebSocket message to all connected clients.
2062
+ *
2063
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2064
+ */
1748
2065
  wssSendRestartNotRequired(snackbar = true) {
1749
2066
  this.log.debug('Sending a restart not required message to all connected clients');
1750
2067
  this.matterbridge.matterbridgeInformation.restartRequired = false;
1751
2068
  if (snackbar === true)
1752
2069
  this.wssSendCloseSnackbarMessage(`Restart required`);
2070
+ // Send the message to all connected clients
1753
2071
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
1754
2072
  }
2073
+ /**
2074
+ * Sends a need to update WebSocket message to all connected clients.
2075
+ *
2076
+ * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2077
+ */
1755
2078
  wssSendUpdateRequired(devVersion = false) {
1756
2079
  this.log.debug('Sending an update required message to all connected clients');
1757
2080
  this.matterbridge.matterbridgeInformation.updateRequired = true;
2081
+ // Send the message to all connected clients
1758
2082
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
1759
2083
  }
2084
+ /**
2085
+ * Sends a cpu update message to all connected clients.
2086
+ *
2087
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2088
+ */
1760
2089
  wssSendCpuUpdate(cpuUsage) {
1761
2090
  if (hasParameter('debug'))
1762
2091
  this.log.debug('Sending a cpu update message to all connected clients');
2092
+ // Send the message to all connected clients
1763
2093
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
1764
2094
  }
2095
+ /**
2096
+ * Sends a memory update message to all connected clients.
2097
+ *
2098
+ * @param {string} totalMemory - The total memory in bytes.
2099
+ * @param {string} freeMemory - The free memory in bytes.
2100
+ * @param {string} rss - The resident set size in bytes.
2101
+ * @param {string} heapTotal - The total heap memory in bytes.
2102
+ * @param {string} heapUsed - The used heap memory in bytes.
2103
+ * @param {string} external - The external memory in bytes.
2104
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2105
+ */
1765
2106
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1766
2107
  if (hasParameter('debug'))
1767
2108
  this.log.debug('Sending a memory update message to all connected clients');
2109
+ // Send the message to all connected clients
1768
2110
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
1769
2111
  }
2112
+ /**
2113
+ * Sends an uptime update message to all connected clients.
2114
+ *
2115
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2116
+ * @param {string} processUptime - The process uptime in a human-readable format.
2117
+ */
1770
2118
  wssSendUptimeUpdate(systemUptime, processUptime) {
1771
2119
  if (hasParameter('debug'))
1772
2120
  this.log.debug('Sending a uptime update message to all connected clients');
2121
+ // Send the message to all connected clients
1773
2122
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
1774
2123
  }
2124
+ /**
2125
+ * Sends an open snackbar message to all connected clients.
2126
+ *
2127
+ * @param {string} message - The message to send.
2128
+ * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2129
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2130
+ * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2131
+ *
2132
+ * @remarks
2133
+ * If timeout is 0, the snackbar message will be displayed until closed by the user.
2134
+ */
1775
2135
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1776
2136
  this.log.debug('Sending a snackbar message to all connected clients');
2137
+ // Send the message to all connected clients
1777
2138
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
1778
2139
  }
2140
+ /**
2141
+ * Sends a close snackbar message to all connected clients.
2142
+ * It will close the snackbar message with the same message and timeout = 0.
2143
+ *
2144
+ * @param {string} message - The message to send.
2145
+ */
1779
2146
  wssSendCloseSnackbarMessage(message) {
1780
2147
  this.log.debug('Sending a close snackbar message to all connected clients');
2148
+ // Send the message to all connected clients
1781
2149
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1782
2150
  }
2151
+ /**
2152
+ * Sends an attribute update message to all connected WebSocket clients.
2153
+ *
2154
+ * @param {string | undefined} plugin - The name of the plugin.
2155
+ * @param {string | undefined} serialNumber - The serial number of the device.
2156
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2157
+ * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2158
+ * @param {string} id - The endpoint id where the attribute belongs.
2159
+ * @param {string} cluster - The cluster name where the attribute belongs.
2160
+ * @param {string} attribute - The name of the attribute that changed.
2161
+ * @param {number | string | boolean} value - The new value of the attribute.
2162
+ *
2163
+ * @remarks
2164
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2165
+ * with the updated attribute information.
2166
+ */
1783
2167
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1784
2168
  this.log.debug('Sending an attribute update message to all connected clients');
2169
+ // Send the message to all connected clients
1785
2170
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1786
2171
  }
2172
+ /**
2173
+ * Sends a message to all connected clients.
2174
+ * This is an helper function to send a broadcast message to all connected clients.
2175
+ *
2176
+ * @param {WsMessageBroadcast} msg - The message to send.
2177
+ */
1787
2178
  wssBroadcastMessage(msg) {
2179
+ // Send the message to all connected clients
1788
2180
  const stringifiedMsg = JSON.stringify(msg);
1789
2181
  if (msg.method !== 'log')
1790
2182
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -1795,3 +2187,4 @@ export class Frontend extends EventEmitter {
1795
2187
  });
1796
2188
  }
1797
2189
  }
2190
+ //# sourceMappingURL=frontend.js.map