matterbridge 3.2.0-dev-20250801-f7eb2a2 → 3.2.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 (252) 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/batteryStorage.d.ts +48 -0
  22. package/dist/devices/batteryStorage.d.ts.map +1 -0
  23. package/dist/devices/batteryStorage.js +48 -1
  24. package/dist/devices/batteryStorage.js.map +1 -0
  25. package/dist/devices/dishwasher.d.ts +91 -0
  26. package/dist/devices/dishwasher.d.ts.map +1 -0
  27. package/dist/devices/dishwasher.js +78 -3
  28. package/dist/devices/dishwasher.js.map +1 -0
  29. package/dist/devices/evse.d.ts +75 -0
  30. package/dist/devices/evse.d.ts.map +1 -0
  31. package/dist/devices/evse.js +74 -10
  32. package/dist/devices/evse.js.map +1 -0
  33. package/dist/devices/export.d.ts +11 -0
  34. package/dist/devices/export.d.ts.map +1 -0
  35. package/dist/devices/export.js +2 -0
  36. package/dist/devices/export.js.map +1 -0
  37. package/dist/devices/extractorHood.d.ts +46 -0
  38. package/dist/devices/extractorHood.d.ts.map +1 -0
  39. package/dist/devices/extractorHood.js +42 -0
  40. package/dist/devices/extractorHood.js.map +1 -0
  41. package/dist/devices/heatPump.d.ts +47 -0
  42. package/dist/devices/heatPump.d.ts.map +1 -0
  43. package/dist/devices/heatPump.js +50 -2
  44. package/dist/devices/heatPump.js.map +1 -0
  45. package/dist/devices/laundryDryer.d.ts +87 -0
  46. package/dist/devices/laundryDryer.d.ts.map +1 -0
  47. package/dist/devices/laundryDryer.js +83 -6
  48. package/dist/devices/laundryDryer.js.map +1 -0
  49. package/dist/devices/laundryWasher.d.ts +242 -0
  50. package/dist/devices/laundryWasher.d.ts.map +1 -0
  51. package/dist/devices/laundryWasher.js +91 -7
  52. package/dist/devices/laundryWasher.js.map +1 -0
  53. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  54. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  55. package/dist/devices/roboticVacuumCleaner.js +93 -7
  56. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  57. package/dist/devices/solarPower.d.ts +40 -0
  58. package/dist/devices/solarPower.d.ts.map +1 -0
  59. package/dist/devices/solarPower.js +38 -0
  60. package/dist/devices/solarPower.js.map +1 -0
  61. package/dist/devices/waterHeater.d.ts +111 -0
  62. package/dist/devices/waterHeater.d.ts.map +1 -0
  63. package/dist/devices/waterHeater.js +82 -2
  64. package/dist/devices/waterHeater.js.map +1 -0
  65. package/dist/dgram/coap.d.ts +205 -0
  66. package/dist/dgram/coap.d.ts.map +1 -0
  67. package/dist/dgram/coap.js +126 -13
  68. package/dist/dgram/coap.js.map +1 -0
  69. package/dist/dgram/dgram.d.ts +140 -0
  70. package/dist/dgram/dgram.d.ts.map +1 -0
  71. package/dist/dgram/dgram.js +113 -2
  72. package/dist/dgram/dgram.js.map +1 -0
  73. package/dist/dgram/mb_coap.d.ts +24 -0
  74. package/dist/dgram/mb_coap.d.ts.map +1 -0
  75. package/dist/dgram/mb_coap.js +41 -3
  76. package/dist/dgram/mb_coap.js.map +1 -0
  77. package/dist/dgram/mb_mdns.d.ts +24 -0
  78. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  79. package/dist/dgram/mb_mdns.js +51 -13
  80. package/dist/dgram/mb_mdns.js.map +1 -0
  81. package/dist/dgram/mdns.d.ts +288 -0
  82. package/dist/dgram/mdns.d.ts.map +1 -0
  83. package/dist/dgram/mdns.js +298 -137
  84. package/dist/dgram/mdns.js.map +1 -0
  85. package/dist/dgram/multicast.d.ts +65 -0
  86. package/dist/dgram/multicast.d.ts.map +1 -0
  87. package/dist/dgram/multicast.js +60 -1
  88. package/dist/dgram/multicast.js.map +1 -0
  89. package/dist/dgram/unicast.d.ts +56 -0
  90. package/dist/dgram/unicast.d.ts.map +1 -0
  91. package/dist/dgram/unicast.js +54 -0
  92. package/dist/dgram/unicast.js.map +1 -0
  93. package/dist/frontend.d.ts +313 -0
  94. package/dist/frontend.d.ts.map +1 -0
  95. package/dist/frontend.js +448 -23
  96. package/dist/frontend.js.map +1 -0
  97. package/dist/globalMatterbridge.d.ts +59 -0
  98. package/dist/globalMatterbridge.d.ts.map +1 -0
  99. package/dist/globalMatterbridge.js +47 -0
  100. package/dist/globalMatterbridge.js.map +1 -0
  101. package/dist/helpers.d.ts +48 -0
  102. package/dist/helpers.d.ts.map +1 -0
  103. package/dist/helpers.js +53 -0
  104. package/dist/helpers.js.map +1 -0
  105. package/dist/index.d.ts +33 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.js +30 -1
  108. package/dist/index.js.map +1 -0
  109. package/dist/logger/export.d.ts +2 -0
  110. package/dist/logger/export.d.ts.map +1 -0
  111. package/dist/logger/export.js +1 -0
  112. package/dist/logger/export.js.map +1 -0
  113. package/dist/matter/behaviors.d.ts +2 -0
  114. package/dist/matter/behaviors.d.ts.map +1 -0
  115. package/dist/matter/behaviors.js +2 -0
  116. package/dist/matter/behaviors.js.map +1 -0
  117. package/dist/matter/clusters.d.ts +2 -0
  118. package/dist/matter/clusters.d.ts.map +1 -0
  119. package/dist/matter/clusters.js +2 -0
  120. package/dist/matter/clusters.js.map +1 -0
  121. package/dist/matter/devices.d.ts +2 -0
  122. package/dist/matter/devices.d.ts.map +1 -0
  123. package/dist/matter/devices.js +2 -0
  124. package/dist/matter/devices.js.map +1 -0
  125. package/dist/matter/endpoints.d.ts +2 -0
  126. package/dist/matter/endpoints.d.ts.map +1 -0
  127. package/dist/matter/endpoints.js +2 -0
  128. package/dist/matter/endpoints.js.map +1 -0
  129. package/dist/matter/export.d.ts +5 -0
  130. package/dist/matter/export.d.ts.map +1 -0
  131. package/dist/matter/export.js +3 -0
  132. package/dist/matter/export.js.map +1 -0
  133. package/dist/matter/types.d.ts +3 -0
  134. package/dist/matter/types.d.ts.map +1 -0
  135. package/dist/matter/types.js +3 -0
  136. package/dist/matter/types.js.map +1 -0
  137. package/dist/matterbridge.d.ts +463 -0
  138. package/dist/matterbridge.d.ts.map +1 -0
  139. package/dist/matterbridge.js +802 -50
  140. package/dist/matterbridge.js.map +1 -0
  141. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  142. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  143. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  144. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  145. package/dist/matterbridgeBehaviors.d.ts +1351 -0
  146. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  147. package/dist/matterbridgeBehaviors.js +65 -5
  148. package/dist/matterbridgeBehaviors.js.map +1 -0
  149. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  150. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  151. package/dist/matterbridgeDeviceTypes.js +579 -15
  152. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  153. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  154. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  155. package/dist/matterbridgeDynamicPlatform.js +36 -0
  156. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  157. package/dist/matterbridgeEndpoint.d.ts +1354 -0
  158. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  159. package/dist/matterbridgeEndpoint.js +1224 -55
  160. package/dist/matterbridgeEndpoint.js.map +1 -0
  161. package/dist/matterbridgeEndpointHelpers.d.ts +406 -0
  162. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  163. package/dist/matterbridgeEndpointHelpers.js +344 -12
  164. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  165. package/dist/matterbridgePlatform.d.ts +310 -0
  166. package/dist/matterbridgePlatform.d.ts.map +1 -0
  167. package/dist/matterbridgePlatform.js +243 -0
  168. package/dist/matterbridgePlatform.js.map +1 -0
  169. package/dist/matterbridgeTypes.d.ts +197 -0
  170. package/dist/matterbridgeTypes.d.ts.map +1 -0
  171. package/dist/matterbridgeTypes.js +25 -0
  172. package/dist/matterbridgeTypes.js.map +1 -0
  173. package/dist/pluginManager.d.ts +270 -0
  174. package/dist/pluginManager.d.ts.map +1 -0
  175. package/dist/pluginManager.js +249 -3
  176. package/dist/pluginManager.js.map +1 -0
  177. package/dist/shelly.d.ts +174 -0
  178. package/dist/shelly.d.ts.map +1 -0
  179. package/dist/shelly.js +168 -7
  180. package/dist/shelly.js.map +1 -0
  181. package/dist/storage/export.d.ts +2 -0
  182. package/dist/storage/export.d.ts.map +1 -0
  183. package/dist/storage/export.js +1 -0
  184. package/dist/storage/export.js.map +1 -0
  185. package/dist/update.d.ts +75 -0
  186. package/dist/update.d.ts.map +1 -0
  187. package/dist/update.js +69 -0
  188. package/dist/update.js.map +1 -0
  189. package/dist/utils/colorUtils.d.ts +117 -0
  190. package/dist/utils/colorUtils.d.ts.map +1 -0
  191. package/dist/utils/colorUtils.js +263 -2
  192. package/dist/utils/colorUtils.js.map +1 -0
  193. package/dist/utils/commandLine.d.ts +59 -0
  194. package/dist/utils/commandLine.d.ts.map +1 -0
  195. package/dist/utils/commandLine.js +54 -0
  196. package/dist/utils/commandLine.js.map +1 -0
  197. package/dist/utils/copyDirectory.d.ts +33 -0
  198. package/dist/utils/copyDirectory.d.ts.map +1 -0
  199. package/dist/utils/copyDirectory.js +38 -1
  200. package/dist/utils/copyDirectory.js.map +1 -0
  201. package/dist/utils/createDirectory.d.ts +34 -0
  202. package/dist/utils/createDirectory.d.ts.map +1 -0
  203. package/dist/utils/createDirectory.js +33 -0
  204. package/dist/utils/createDirectory.js.map +1 -0
  205. package/dist/utils/createZip.d.ts +39 -0
  206. package/dist/utils/createZip.d.ts.map +1 -0
  207. package/dist/utils/createZip.js +47 -2
  208. package/dist/utils/createZip.js.map +1 -0
  209. package/dist/utils/deepCopy.d.ts +32 -0
  210. package/dist/utils/deepCopy.d.ts.map +1 -0
  211. package/dist/utils/deepCopy.js +39 -0
  212. package/dist/utils/deepCopy.js.map +1 -0
  213. package/dist/utils/deepEqual.d.ts +54 -0
  214. package/dist/utils/deepEqual.d.ts.map +1 -0
  215. package/dist/utils/deepEqual.js +72 -1
  216. package/dist/utils/deepEqual.js.map +1 -0
  217. package/dist/utils/error.d.ts +44 -0
  218. package/dist/utils/error.d.ts.map +1 -0
  219. package/dist/utils/error.js +41 -0
  220. package/dist/utils/error.js.map +1 -0
  221. package/dist/utils/export.d.ts +12 -0
  222. package/dist/utils/export.d.ts.map +1 -0
  223. package/dist/utils/export.js +1 -0
  224. package/dist/utils/export.js.map +1 -0
  225. package/dist/utils/hex.d.ts +89 -0
  226. package/dist/utils/hex.d.ts.map +1 -0
  227. package/dist/utils/hex.js +123 -0
  228. package/dist/utils/hex.js.map +1 -0
  229. package/dist/utils/isvalid.d.ts +103 -0
  230. package/dist/utils/isvalid.d.ts.map +1 -0
  231. package/dist/utils/isvalid.js +101 -0
  232. package/dist/utils/isvalid.js.map +1 -0
  233. package/dist/utils/network.d.ts +84 -0
  234. package/dist/utils/network.d.ts.map +1 -0
  235. package/dist/utils/network.js +94 -7
  236. package/dist/utils/network.js.map +1 -0
  237. package/dist/utils/spawn.d.ts +33 -0
  238. package/dist/utils/spawn.d.ts.map +1 -0
  239. package/dist/utils/spawn.js +40 -0
  240. package/dist/utils/spawn.js.map +1 -0
  241. package/dist/utils/wait.d.ts +56 -0
  242. package/dist/utils/wait.d.ts.map +1 -0
  243. package/dist/utils/wait.js +62 -9
  244. package/dist/utils/wait.js.map +1 -0
  245. package/docs/README-DEV.md +377 -0
  246. package/docs/README-DOCKER.md +208 -0
  247. package/docs/README-NGINX.md +235 -0
  248. package/docs/README-PODMAN.md +109 -0
  249. package/docs/README-SERVICE.md +212 -0
  250. package/docs/README.md +618 -0
  251. package/npm-shrinkwrap.json +2 -2
  252. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,30 +1,126 @@
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.2.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';
4
28
  import path from 'node:path';
5
29
  import { existsSync, promises as fs } from 'node:fs';
6
30
  import EventEmitter from 'node:events';
31
+ // Third-party modules
7
32
  import express from 'express';
8
33
  import WebSocket, { WebSocketServer } from 'ws';
9
34
  import multer from 'multer';
35
+ // AnsiLogger module
10
36
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
37
+ // @matter
11
38
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
12
39
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
40
+ // Matterbridge
13
41
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
14
42
  import { plg } from './matterbridgeTypes.js';
15
43
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
16
44
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
45
+ /**
46
+ * Websocket message ID for logging.
47
+ *
48
+ * @constant {number}
49
+ */
17
50
  export const WS_ID_LOG = 0;
51
+ /**
52
+ * Websocket message ID indicating a refresh is needed.
53
+ *
54
+ * @constant {number}
55
+ */
18
56
  export const WS_ID_REFRESH_NEEDED = 1;
57
+ /**
58
+ * Websocket message ID indicating a restart is needed.
59
+ *
60
+ * @constant {number}
61
+ */
19
62
  export const WS_ID_RESTART_NEEDED = 2;
63
+ /**
64
+ * Websocket message ID indicating a cpu update.
65
+ *
66
+ * @constant {number}
67
+ */
20
68
  export const WS_ID_CPU_UPDATE = 3;
69
+ /**
70
+ * Websocket message ID indicating a memory update.
71
+ *
72
+ * @constant {number}
73
+ */
21
74
  export const WS_ID_MEMORY_UPDATE = 4;
75
+ /**
76
+ * Websocket message ID indicating an uptime update.
77
+ *
78
+ * @constant {number}
79
+ */
22
80
  export const WS_ID_UPTIME_UPDATE = 5;
81
+ /**
82
+ * Websocket message ID indicating a snackbar message.
83
+ *
84
+ * @constant {number}
85
+ */
23
86
  export const WS_ID_SNACKBAR = 6;
87
+ /**
88
+ * Websocket message ID indicating matterbridge has un update available.
89
+ *
90
+ * @constant {number}
91
+ */
24
92
  export const WS_ID_UPDATE_NEEDED = 7;
93
+ /**
94
+ * Websocket message ID indicating a state update.
95
+ *
96
+ * @constant {number}
97
+ */
25
98
  export const WS_ID_STATEUPDATE = 8;
99
+ /**
100
+ * Websocket message ID indicating to close a permanent snackbar message.
101
+ *
102
+ * @constant {number}
103
+ */
26
104
  export const WS_ID_CLOSE_SNACKBAR = 9;
105
+ /**
106
+ * Websocket message ID indicating a shelly system update.
107
+ * check:
108
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
109
+ * perform:
110
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
111
+ *
112
+ * @constant {number}
113
+ */
27
114
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
115
+ /**
116
+ * Websocket message ID indicating a shelly main update.
117
+ * check:
118
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
119
+ * perform:
120
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
121
+ *
122
+ * @constant {number}
123
+ */
28
124
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
29
125
  export class Frontend extends EventEmitter {
30
126
  matterbridge;
@@ -37,7 +133,7 @@ export class Frontend extends EventEmitter {
37
133
  constructor(matterbridge) {
38
134
  super();
39
135
  this.matterbridge = matterbridge;
40
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
136
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
41
137
  }
42
138
  set logLevel(logLevel) {
43
139
  this.log.logLevel = logLevel;
@@ -45,12 +141,41 @@ export class Frontend extends EventEmitter {
45
141
  async start(port = 8283) {
46
142
  this.port = port;
47
143
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
144
+ // Initialize multer with the upload directory
48
145
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
49
146
  await fs.mkdir(uploadDir, { recursive: true });
50
147
  const upload = multer({ dest: uploadDir });
148
+ // Create the express app that serves the frontend
51
149
  this.expressApp = express();
150
+ // Inject logging/debug wrapper for route/middleware registration
151
+ /*
152
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
153
+ for (const method of methods) {
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
158
+ try {
159
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
160
+ return original(path, ...rest);
161
+ } catch (err) {
162
+ console.error(`[ERROR] Failed to register route: ${path}`);
163
+ throw err;
164
+ }
165
+ };
166
+ }
167
+ */
168
+ // Log all requests to the server for debugging
169
+ /*
170
+ this.expressApp.use((req, res, next) => {
171
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
172
+ next();
173
+ });
174
+ */
175
+ // Serve static files from '/static' endpoint
52
176
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
53
177
  if (!hasParameter('ssl')) {
178
+ // Create an HTTP server and attach the express app
54
179
  try {
55
180
  this.log.debug(`Creating HTTP server...`);
56
181
  this.httpServer = createServer(this.expressApp);
@@ -60,6 +185,7 @@ export class Frontend extends EventEmitter {
60
185
  this.emit('server_error', error);
61
186
  return;
62
187
  }
188
+ // Listen on the specified port
63
189
  if (hasParameter('ingress')) {
64
190
  this.httpServer.listen(this.port, '0.0.0.0', () => {
65
191
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -98,6 +224,7 @@ export class Frontend extends EventEmitter {
98
224
  let passphrase;
99
225
  let httpsServerOptions = {};
100
226
  if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
227
+ // Load the p12 certificate and the passphrase
101
228
  try {
102
229
  pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
103
230
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -109,7 +236,7 @@ export class Frontend extends EventEmitter {
109
236
  }
110
237
  try {
111
238
  passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
112
- passphrase = passphrase.trim();
239
+ passphrase = passphrase.trim(); // Ensure no extra characters
113
240
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
114
241
  }
115
242
  catch (error) {
@@ -120,6 +247,7 @@ export class Frontend extends EventEmitter {
120
247
  httpsServerOptions = { pfx, passphrase };
121
248
  }
122
249
  else {
250
+ // 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.
123
251
  try {
124
252
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
125
253
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -149,9 +277,10 @@ export class Frontend extends EventEmitter {
149
277
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
150
278
  }
151
279
  if (hasParameter('mtls')) {
152
- httpsServerOptions.requestCert = true;
153
- httpsServerOptions.rejectUnauthorized = true;
280
+ httpsServerOptions.requestCert = true; // Request client certificate
281
+ httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
154
282
  }
283
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
155
284
  try {
156
285
  this.log.debug(`Creating HTTPS server...`);
157
286
  this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
@@ -161,6 +290,7 @@ export class Frontend extends EventEmitter {
161
290
  this.emit('server_error', error);
162
291
  return;
163
292
  }
293
+ // Listen on the specified port
164
294
  if (hasParameter('ingress')) {
165
295
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
166
296
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -190,17 +320,19 @@ export class Frontend extends EventEmitter {
190
320
  return;
191
321
  });
192
322
  }
323
+ // Create a WebSocket server and attach it to the http or https server
193
324
  const wssPort = this.port;
194
325
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
195
326
  this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
196
327
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
197
328
  this.webSocketServer.on('connection', (ws, request) => {
198
329
  const clientIp = request.socket.remoteAddress;
199
- let callbackLogLevel = "notice";
200
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
201
- callbackLogLevel = "info";
202
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
203
- callbackLogLevel = "debug";
330
+ // Set the global logger callback for the WebSocketServer
331
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
332
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
333
+ callbackLogLevel = "info" /* LogLevel.INFO */;
334
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
335
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
204
336
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
205
337
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
206
338
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -222,6 +354,7 @@ export class Frontend extends EventEmitter {
222
354
  }
223
355
  });
224
356
  ws.on('error', (error) => {
357
+ // istanbul ignore next
225
358
  this.log.error(`WebSocket client error: ${error}`);
226
359
  });
227
360
  });
@@ -235,6 +368,7 @@ export class Frontend extends EventEmitter {
235
368
  this.webSocketServer.on('error', (ws, error) => {
236
369
  this.log.error(`WebSocketServer error: ${error}`);
237
370
  });
371
+ // Subscribe to cli events
238
372
  cliEmitter.removeAllListeners();
239
373
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
240
374
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -245,6 +379,8 @@ export class Frontend extends EventEmitter {
245
379
  cliEmitter.on('cpu', (cpuUsage) => {
246
380
  this.wssSendCpuUpdate(cpuUsage);
247
381
  });
382
+ // Endpoint to validate login code
383
+ // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
248
384
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
249
385
  const { password } = req.body;
250
386
  this.log.debug('The frontend sent /api/login', password);
@@ -263,23 +399,27 @@ export class Frontend extends EventEmitter {
263
399
  this.log.warn('/api/login error wrong password');
264
400
  res.json({ valid: false });
265
401
  }
402
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
266
403
  }
267
404
  catch (error) {
268
405
  this.log.error('/api/login error getting password');
269
406
  res.json({ valid: false });
270
407
  }
271
408
  });
409
+ // Endpoint to provide health check for docker
272
410
  this.expressApp.get('/health', (req, res) => {
273
411
  this.log.debug('Express received /health');
274
412
  const healthStatus = {
275
- status: 'ok',
276
- uptime: process.uptime(),
277
- timestamp: new Date().toISOString(),
413
+ status: 'ok', // Indicate service is healthy
414
+ uptime: process.uptime(), // Server uptime in seconds
415
+ timestamp: new Date().toISOString(), // Current timestamp
278
416
  };
279
417
  res.status(200).json(healthStatus);
280
418
  });
419
+ // Endpoint to provide memory usage details
281
420
  this.expressApp.get('/memory', async (req, res) => {
282
421
  this.log.debug('Express received /memory');
422
+ // Memory usage from process
283
423
  const memoryUsageRaw = process.memoryUsage();
284
424
  const memoryUsage = {
285
425
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -288,10 +428,13 @@ export class Frontend extends EventEmitter {
288
428
  external: this.formatMemoryUsage(memoryUsageRaw.external),
289
429
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
290
430
  };
431
+ // V8 heap statistics
291
432
  const { default: v8 } = await import('node:v8');
292
433
  const heapStatsRaw = v8.getHeapStatistics();
293
434
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
435
+ // Format heapStats
294
436
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
437
+ // Format heapSpaces
295
438
  const heapSpaces = heapSpacesRaw.map((space) => ({
296
439
  ...space,
297
440
  space_size: this.formatMemoryUsage(space.space_size),
@@ -309,19 +452,23 @@ export class Frontend extends EventEmitter {
309
452
  };
310
453
  res.status(200).json(memoryReport);
311
454
  });
455
+ // Endpoint to provide settings
312
456
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
313
457
  this.log.debug('The frontend sent /api/settings');
314
458
  res.json(await this.getApiSettings());
315
459
  });
460
+ // Endpoint to provide plugins
316
461
  this.expressApp.get('/api/plugins', async (req, res) => {
317
462
  this.log.debug('The frontend sent /api/plugins');
318
463
  res.json(this.getPlugins());
319
464
  });
465
+ // Endpoint to provide devices
320
466
  this.expressApp.get('/api/devices', async (req, res) => {
321
467
  this.log.debug('The frontend sent /api/devices');
322
468
  const devices = await this.getDevices();
323
469
  res.json(devices);
324
470
  });
471
+ // Endpoint to view the matterbridge log
325
472
  this.expressApp.get('/api/view-mblog', async (req, res) => {
326
473
  this.log.debug('The frontend sent /api/view-mblog');
327
474
  try {
@@ -334,6 +481,7 @@ export class Frontend extends EventEmitter {
334
481
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
335
482
  }
336
483
  });
484
+ // Endpoint to view the matter.js log
337
485
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
338
486
  this.log.debug('The frontend sent /api/view-mjlog');
339
487
  try {
@@ -346,6 +494,7 @@ export class Frontend extends EventEmitter {
346
494
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
347
495
  }
348
496
  });
497
+ // Endpoint to view the shelly log
349
498
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
350
499
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
351
500
  try {
@@ -358,6 +507,7 @@ export class Frontend extends EventEmitter {
358
507
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
359
508
  }
360
509
  });
510
+ // Endpoint to download the matterbridge log
361
511
  this.expressApp.get('/api/download-mblog', async (req, res) => {
362
512
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
363
513
  try {
@@ -371,12 +521,14 @@ export class Frontend extends EventEmitter {
371
521
  }
372
522
  res.type('text/plain');
373
523
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
524
+ /* istanbul ignore if */
374
525
  if (error) {
375
526
  this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
376
527
  res.status(500).send('Error downloading the matterbridge log file');
377
528
  }
378
529
  });
379
530
  });
531
+ // Endpoint to download the matter log
380
532
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
381
533
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
382
534
  try {
@@ -390,12 +542,14 @@ export class Frontend extends EventEmitter {
390
542
  }
391
543
  res.type('text/plain');
392
544
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
545
+ /* istanbul ignore if */
393
546
  if (error) {
394
547
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
395
548
  res.status(500).send('Error downloading the matter log file');
396
549
  }
397
550
  });
398
551
  });
552
+ // Endpoint to download the shelly log
399
553
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
400
554
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
401
555
  try {
@@ -409,74 +563,90 @@ export class Frontend extends EventEmitter {
409
563
  }
410
564
  res.type('text/plain');
411
565
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
566
+ /* istanbul ignore if */
412
567
  if (error) {
413
568
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
414
569
  res.status(500).send('Error downloading Shelly system log file');
415
570
  }
416
571
  });
417
572
  });
573
+ // Endpoint to download the matterbridge storage directory
418
574
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
419
575
  this.log.debug('The frontend sent /api/download-mbstorage');
420
576
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
421
577
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
578
+ /* istanbul ignore if */
422
579
  if (error) {
423
580
  this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
424
581
  res.status(500).send('Error downloading the matterbridge storage file');
425
582
  }
426
583
  });
427
584
  });
585
+ // Endpoint to download the matter storage file
428
586
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
429
587
  this.log.debug('The frontend sent /api/download-mjstorage');
430
588
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
431
589
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
590
+ /* istanbul ignore if */
432
591
  if (error) {
433
592
  this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
434
593
  res.status(500).send('Error downloading the matter storage zip file');
435
594
  }
436
595
  });
437
596
  });
597
+ // Endpoint to download the matterbridge plugin directory
438
598
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
439
599
  this.log.debug('The frontend sent /api/download-pluginstorage');
440
600
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
441
601
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
602
+ /* istanbul ignore if */
442
603
  if (error) {
443
604
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
444
605
  res.status(500).send('Error downloading the matterbridge plugin storage file');
445
606
  }
446
607
  });
447
608
  });
609
+ // Endpoint to download the matterbridge plugin config files
448
610
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
449
611
  this.log.debug('The frontend sent /api/download-pluginconfig');
450
612
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
451
613
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
614
+ /* istanbul ignore if */
452
615
  if (error) {
453
616
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
454
617
  res.status(500).send('Error downloading the matterbridge plugin config file');
455
618
  }
456
619
  });
457
620
  });
621
+ // Endpoint to download the matterbridge backup (created with the backup command)
458
622
  this.expressApp.get('/api/download-backup', async (req, res) => {
459
623
  this.log.debug('The frontend sent /api/download-backup');
460
624
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
625
+ /* istanbul ignore if */
461
626
  if (error) {
462
627
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
463
628
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
464
629
  }
465
630
  });
466
631
  });
632
+ // Endpoint to upload a package
467
633
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
468
634
  const { filename } = req.body;
469
635
  const file = req.file;
636
+ /* istanbul ignore if */
470
637
  if (!file || !filename) {
471
638
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
472
639
  res.status(400).send('Invalid request: file and filename are required');
473
640
  return;
474
641
  }
475
642
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
643
+ // Define the path where the plugin file will be saved
476
644
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
477
645
  try {
646
+ // Move the uploaded file to the specified path
478
647
  await fs.rename(file.path, filePath);
479
648
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
649
+ // Install the plugin package
480
650
  if (filename.endsWith('.tgz')) {
481
651
  const { spawnCommand } = await import('./utils/spawn.js');
482
652
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
@@ -496,6 +666,7 @@ export class Frontend extends EventEmitter {
496
666
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
497
667
  }
498
668
  });
669
+ // Fallback for routing (must be the last route)
499
670
  this.expressApp.use((req, res) => {
500
671
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
501
672
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -504,13 +675,16 @@ export class Frontend extends EventEmitter {
504
675
  }
505
676
  async stop() {
506
677
  this.log.debug('Stopping the frontend...');
678
+ // Remove listeners from the express app
507
679
  if (this.expressApp) {
508
680
  this.expressApp.removeAllListeners();
509
681
  this.expressApp = undefined;
510
682
  this.log.debug('Frontend app closed successfully');
511
683
  }
684
+ // Close the WebSocket server
512
685
  if (this.webSocketServer) {
513
686
  this.log.debug('Closing WebSocket server...');
687
+ // Close all active connections
514
688
  this.webSocketServer.clients.forEach((client) => {
515
689
  if (client.readyState === WebSocket.OPEN) {
516
690
  client.close();
@@ -519,6 +693,7 @@ export class Frontend extends EventEmitter {
519
693
  await withTimeout(new Promise((resolve) => {
520
694
  this.webSocketServer?.close((error) => {
521
695
  if (error) {
696
+ // istanbul ignore next
522
697
  this.log.error(`Error closing WebSocket server: ${error}`);
523
698
  }
524
699
  else {
@@ -531,11 +706,13 @@ export class Frontend extends EventEmitter {
531
706
  this.webSocketServer.removeAllListeners();
532
707
  this.webSocketServer = undefined;
533
708
  }
709
+ // Close the http server
534
710
  if (this.httpServer) {
535
711
  this.log.debug('Closing http server...');
536
712
  await withTimeout(new Promise((resolve) => {
537
713
  this.httpServer?.close((error) => {
538
714
  if (error) {
715
+ // istanbul ignore next
539
716
  this.log.error(`Error closing http server: ${error}`);
540
717
  }
541
718
  else {
@@ -549,11 +726,13 @@ export class Frontend extends EventEmitter {
549
726
  this.httpServer = undefined;
550
727
  this.log.debug('Frontend http server closed successfully');
551
728
  }
729
+ // Close the https server
552
730
  if (this.httpsServer) {
553
731
  this.log.debug('Closing https server...');
554
732
  await withTimeout(new Promise((resolve) => {
555
733
  this.httpsServer?.close((error) => {
556
734
  if (error) {
735
+ // istanbul ignore next
557
736
  this.log.error(`Error closing https server: ${error}`);
558
737
  }
559
738
  else {
@@ -569,6 +748,7 @@ export class Frontend extends EventEmitter {
569
748
  }
570
749
  this.log.debug('Frontend stopped successfully');
571
750
  }
751
+ // Function to format bytes to KB, MB, or GB
572
752
  formatMemoryUsage = (bytes) => {
573
753
  if (bytes >= 1024 ** 3) {
574
754
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -580,6 +760,7 @@ export class Frontend extends EventEmitter {
580
760
  return `${(bytes / 1024).toFixed(2)} KB`;
581
761
  }
582
762
  };
763
+ // Function to format system uptime with only the most significant unit
583
764
  formatOsUpTime = (seconds) => {
584
765
  if (seconds >= 86400) {
585
766
  const days = Math.floor(seconds / 86400);
@@ -595,7 +776,13 @@ export class Frontend extends EventEmitter {
595
776
  }
596
777
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
597
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
+ */
598
784
  async getApiSettings() {
785
+ // Update the system information
599
786
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
600
787
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
601
788
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -604,6 +791,7 @@ export class Frontend extends EventEmitter {
604
791
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
605
792
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
606
793
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
794
+ // Update the matterbridge information
607
795
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
608
796
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
609
797
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
@@ -615,6 +803,7 @@ export class Frontend extends EventEmitter {
615
803
  this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
616
804
  this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
617
805
  this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
806
+ // Update the matterbridge information in bridge mode
618
807
  if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
619
808
  this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
620
809
  this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
@@ -624,6 +813,12 @@ export class Frontend extends EventEmitter {
624
813
  }
625
814
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
626
815
  }
816
+ /**
817
+ * Retrieves the reachable attribute.
818
+ *
819
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
820
+ * @returns {boolean} The reachable attribute.
821
+ */
627
822
  getReachability(device) {
628
823
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
629
824
  return false;
@@ -635,6 +830,12 @@ export class Frontend extends EventEmitter {
635
830
  return true;
636
831
  return false;
637
832
  }
833
+ /**
834
+ * Retrieves the power source attribute.
835
+ *
836
+ * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
837
+ * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
838
+ */
638
839
  getPowerSource(endpoint) {
639
840
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
640
841
  return undefined;
@@ -650,13 +851,21 @@ export class Frontend extends EventEmitter {
650
851
  }
651
852
  return;
652
853
  };
854
+ // Root endpoint
653
855
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
654
856
  return powerSource(endpoint);
857
+ // Child endpoints
655
858
  for (const child of endpoint.getChildEndpoints()) {
656
859
  if (child.hasClusterServer(PowerSource.Cluster.id))
657
860
  return powerSource(child);
658
861
  }
659
862
  }
863
+ /**
864
+ * Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
865
+ *
866
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
867
+ * @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
868
+ */
660
869
  getMatterDataFromDevice(device) {
661
870
  if (device.mode === 'server' && device.serverNode) {
662
871
  return {
@@ -668,6 +877,13 @@ export class Frontend extends EventEmitter {
668
877
  };
669
878
  }
670
879
  }
880
+ /**
881
+ * Retrieves the cluster text description from a given device.
882
+ * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
883
+ *
884
+ * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
885
+ * @returns {string} The attributes description of the cluster servers in the device.
886
+ */
671
887
  getClusterTextFromDevice(device) {
672
888
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
673
889
  return '';
@@ -678,6 +894,7 @@ export class Frontend extends EventEmitter {
678
894
  if (composed)
679
895
  return 'Composed: ' + composed.value;
680
896
  }
897
+ // istanbul ignore next cause is not reachable
681
898
  return '';
682
899
  };
683
900
  const getFixedLabel = (device) => {
@@ -687,11 +904,13 @@ export class Frontend extends EventEmitter {
687
904
  if (composed)
688
905
  return 'Composed: ' + composed.value;
689
906
  }
907
+ // istanbul ignore next cause is not reacheable
690
908
  return '';
691
909
  };
692
910
  let attributes = '';
693
911
  let supportedModes = [];
694
912
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
913
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
695
914
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
696
915
  return;
697
916
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -781,11 +1000,17 @@ export class Frontend extends EventEmitter {
781
1000
  if (clusterName === 'userLabel' && attributeName === 'labelList')
782
1001
  attributes += `${getUserLabel(device)} `;
783
1002
  });
1003
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
784
1004
  return attributes.trimStart().trimEnd();
785
1005
  }
1006
+ /**
1007
+ * Retrieves the registered plugins sanitized for res.json().
1008
+ *
1009
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1010
+ */
786
1011
  getPlugins() {
787
1012
  if (this.matterbridge.hasCleanupStarted)
788
- return [];
1013
+ return []; // Skip if cleanup has started
789
1014
  const baseRegisteredPlugins = [];
790
1015
  for (const plugin of this.matterbridge.plugins) {
791
1016
  baseRegisteredPlugins.push({
@@ -815,6 +1040,7 @@ export class Frontend extends EventEmitter {
815
1040
  schemaJson: plugin.schemaJson,
816
1041
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
817
1042
  hasBlackList: plugin.configJson?.blackList !== undefined,
1043
+ // Childbridge mode specific data
818
1044
  paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
819
1045
  qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
820
1046
  manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
@@ -824,13 +1050,21 @@ export class Frontend extends EventEmitter {
824
1050
  }
825
1051
  return baseRegisteredPlugins;
826
1052
  }
1053
+ /**
1054
+ * Retrieves the devices from Matterbridge.
1055
+ *
1056
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
1057
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1058
+ */
827
1059
  async getDevices(pluginName) {
828
1060
  if (this.matterbridge.hasCleanupStarted)
829
- return [];
1061
+ return []; // Skip if cleanup has started
830
1062
  const devices = [];
831
1063
  for (const device of this.matterbridge.devices.array()) {
1064
+ // Filter by pluginName if provided
832
1065
  if (pluginName && pluginName !== device.plugin)
833
1066
  continue;
1067
+ // Check if the device has the required properties
834
1068
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
835
1069
  continue;
836
1070
  devices.push({
@@ -850,22 +1084,37 @@ export class Frontend extends EventEmitter {
850
1084
  }
851
1085
  return devices;
852
1086
  }
1087
+ /**
1088
+ * Retrieves the clusters from a given plugin and endpoint number.
1089
+ *
1090
+ * Response for /api/clusters
1091
+ *
1092
+ * @param {string} pluginName - The name of the plugin.
1093
+ * @param {number} endpointNumber - The endpoint number.
1094
+ * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1095
+ */
853
1096
  getClusters(pluginName, endpointNumber) {
854
1097
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
855
1098
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
856
1099
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
857
1100
  return;
858
1101
  }
1102
+ // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1103
+ // Get the device types from the main endpoint
859
1104
  const deviceTypes = [];
860
1105
  const clusters = [];
861
1106
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
862
1107
  deviceTypes.push(d.deviceType);
863
1108
  });
1109
+ // Get the clusters from the main endpoint
864
1110
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
865
1111
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
866
1112
  return;
867
1113
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
868
1114
  return;
1115
+ // console.log(
1116
+ // `${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}`,
1117
+ // );
869
1118
  clusters.push({
870
1119
  endpoint: endpoint.number.toString(),
871
1120
  id: 'main',
@@ -878,12 +1127,19 @@ export class Frontend extends EventEmitter {
878
1127
  attributeLocalValue: attributeValue,
879
1128
  });
880
1129
  });
1130
+ // Get the child endpoints
881
1131
  const childEndpoints = endpoint.getChildEndpoints();
1132
+ // if (childEndpoints.length === 0) {
1133
+ // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1134
+ // }
882
1135
  childEndpoints.forEach((childEndpoint) => {
1136
+ // istanbul ignore if cause is not reachable: should never happen but ...
883
1137
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
884
1138
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
885
1139
  return;
886
1140
  }
1141
+ // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1142
+ // Get the device types of the child endpoint
887
1143
  const deviceTypes = [];
888
1144
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
889
1145
  deviceTypes.push(d.deviceType);
@@ -893,9 +1149,12 @@ export class Frontend extends EventEmitter {
893
1149
  return;
894
1150
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
895
1151
  return;
1152
+ // console.log(
1153
+ // `${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}`,
1154
+ // );
896
1155
  clusters.push({
897
1156
  endpoint: childEndpoint.number.toString(),
898
- id: childEndpoint.maybeId ?? 'null',
1157
+ id: childEndpoint.maybeId ?? 'null', // Never happens
899
1158
  deviceTypes,
900
1159
  clusterName: capitalizeFirstLetter(clusterName),
901
1160
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -908,6 +1167,13 @@ export class Frontend extends EventEmitter {
908
1167
  });
909
1168
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
910
1169
  }
1170
+ /**
1171
+ * Handles incoming websocket messages for the Matterbridge frontend.
1172
+ *
1173
+ * @param {WebSocket} client - The websocket client that sent the message.
1174
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1175
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1176
+ */
911
1177
  async wsMessageHandler(client, message) {
912
1178
  let data;
913
1179
  try {
@@ -954,33 +1220,45 @@ export class Frontend extends EventEmitter {
954
1220
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
955
1221
  const packageName = data.params.packageName.replace(/@.*$/, '');
956
1222
  if (data.params.restart === false && packageName !== 'matterbridge') {
1223
+ // The install comes from InstallPlugins
957
1224
  this.matterbridge.plugins
958
1225
  .add(packageName)
959
1226
  .then((plugin) => {
1227
+ // istanbul ignore next if
960
1228
  if (plugin) {
1229
+ // The plugin is not registered
961
1230
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
962
1231
  this.matterbridge.plugins
963
1232
  .load(plugin, true, 'The plugin has been added', true)
1233
+ // eslint-disable-next-line promise/no-nesting
964
1234
  .then(() => {
965
1235
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
966
1236
  this.wssSendRefreshRequired('plugins');
967
1237
  return;
968
1238
  })
1239
+ // eslint-disable-next-line promise/no-nesting
969
1240
  .catch((_error) => {
1241
+ //
970
1242
  });
971
1243
  }
972
1244
  else {
1245
+ // The plugin is already registered
973
1246
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
974
1247
  this.wssSendRefreshRequired('plugins');
975
1248
  this.wssSendRestartRequired(true, true);
976
1249
  }
977
1250
  return;
978
1251
  })
1252
+ // eslint-disable-next-line promise/no-nesting
979
1253
  .catch((_error) => {
1254
+ //
980
1255
  });
981
1256
  }
982
1257
  else {
1258
+ // The package is matterbridge
1259
+ // istanbul ignore next
983
1260
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1261
+ // istanbul ignore next if
984
1262
  if (this.matterbridge.restartMode !== '') {
985
1263
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
986
1264
  this.matterbridge.shutdownProcess();
@@ -1002,7 +1280,9 @@ export class Frontend extends EventEmitter {
1002
1280
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1003
1281
  return;
1004
1282
  }
1283
+ // The package is a plugin
1005
1284
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1285
+ // istanbul ignore next if
1006
1286
  if (plugin) {
1007
1287
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1008
1288
  await this.matterbridge.plugins.remove(data.params.packageName);
@@ -1010,6 +1290,7 @@ export class Frontend extends EventEmitter {
1010
1290
  this.wssSendRefreshRequired('plugins');
1011
1291
  this.wssSendRefreshRequired('devices');
1012
1292
  }
1293
+ // Uninstall the package
1013
1294
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1014
1295
  const { spawnCommand } = await import('./utils/spawn.js');
1015
1296
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1050,6 +1331,7 @@ export class Frontend extends EventEmitter {
1050
1331
  return;
1051
1332
  })
1052
1333
  .catch((_error) => {
1334
+ //
1053
1335
  });
1054
1336
  }
1055
1337
  else {
@@ -1096,6 +1378,7 @@ export class Frontend extends EventEmitter {
1096
1378
  return;
1097
1379
  })
1098
1380
  .catch((_error) => {
1381
+ //
1099
1382
  });
1100
1383
  }
1101
1384
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1121,6 +1404,7 @@ export class Frontend extends EventEmitter {
1121
1404
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1122
1405
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1123
1406
  if (plugin.serverNode) {
1407
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1124
1408
  await this.matterbridge.stopServerNode(plugin.serverNode);
1125
1409
  plugin.serverNode = undefined;
1126
1410
  }
@@ -1131,15 +1415,16 @@ export class Frontend extends EventEmitter {
1131
1415
  }
1132
1416
  }
1133
1417
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1134
- plugin.restartRequired = false;
1418
+ plugin.restartRequired = false; // Reset plugin restartRequired
1135
1419
  let needRestart = 0;
1136
1420
  for (const plugin of this.matterbridge.plugins) {
1137
1421
  if (plugin.restartRequired)
1138
1422
  needRestart++;
1139
1423
  }
1140
1424
  if (needRestart === 0) {
1141
- this.wssSendRestartNotRequired(true);
1425
+ this.wssSendRestartNotRequired(true); // Reset global restart required message
1142
1426
  }
1427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1143
1428
  if (plugin.serverNode)
1144
1429
  await this.matterbridge.startServerNode(plugin.serverNode);
1145
1430
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1245,6 +1530,8 @@ export class Frontend extends EventEmitter {
1245
1530
  else if (data.method === '/api/advertise') {
1246
1531
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
1247
1532
  this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
1533
+ // this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
1534
+ // this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
1248
1535
  this.wssSendRefreshRequired('matterbridgeAdvertise');
1249
1536
  this.wssSendSnackbarMessage(`Started fabrics share`, 0);
1250
1537
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
@@ -1367,22 +1654,22 @@ export class Frontend extends EventEmitter {
1367
1654
  if (isValidString(data.params.value, 4)) {
1368
1655
  this.log.debug('Matterbridge logger level:', data.params.value);
1369
1656
  if (data.params.value === 'Debug') {
1370
- await this.matterbridge.setLogLevel("debug");
1657
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1371
1658
  }
1372
1659
  else if (data.params.value === 'Info') {
1373
- await this.matterbridge.setLogLevel("info");
1660
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1374
1661
  }
1375
1662
  else if (data.params.value === 'Notice') {
1376
- await this.matterbridge.setLogLevel("notice");
1663
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1377
1664
  }
1378
1665
  else if (data.params.value === 'Warn') {
1379
- await this.matterbridge.setLogLevel("warn");
1666
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1380
1667
  }
1381
1668
  else if (data.params.value === 'Error') {
1382
- await this.matterbridge.setLogLevel("error");
1669
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1383
1670
  }
1384
1671
  else if (data.params.value === 'Fatal') {
1385
- await this.matterbridge.setLogLevel("fatal");
1672
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1386
1673
  }
1387
1674
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1388
1675
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1393,6 +1680,7 @@ export class Frontend extends EventEmitter {
1393
1680
  this.log.debug('Matterbridge file log:', data.params.value);
1394
1681
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1395
1682
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1683
+ // Create the file logger for matterbridge
1396
1684
  if (data.params.value)
1397
1685
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1398
1686
  else
@@ -1439,6 +1727,7 @@ export class Frontend extends EventEmitter {
1439
1727
  });
1440
1728
  }
1441
1729
  catch (error) {
1730
+ /* istanbul ignore next */
1442
1731
  this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1443
1732
  }
1444
1733
  }
@@ -1447,6 +1736,7 @@ export class Frontend extends EventEmitter {
1447
1736
  Logger.removeLogger('matterfilelogger');
1448
1737
  }
1449
1738
  catch (error) {
1739
+ /* istanbul ignore next */
1450
1740
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1451
1741
  }
1452
1742
  }
@@ -1559,15 +1849,19 @@ export class Frontend extends EventEmitter {
1559
1849
  return;
1560
1850
  }
1561
1851
  const config = plugin.configJson;
1852
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1562
1853
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1854
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1563
1855
  if (select === 'serial')
1564
1856
  this.log.info(`Selected device serial ${data.params.serial}`);
1565
1857
  if (select === 'name')
1566
1858
  this.log.info(`Selected device name ${data.params.name}`);
1567
1859
  if (config && select && (select === 'serial' || select === 'name')) {
1860
+ // Remove postfix from the serial if it exists
1568
1861
  if (config.postfix) {
1569
1862
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1570
1863
  }
1864
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1571
1865
  if (isValidArray(config.whiteList, 1)) {
1572
1866
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1573
1867
  config.whiteList.push(data.params.serial);
@@ -1576,6 +1870,7 @@ export class Frontend extends EventEmitter {
1576
1870
  config.whiteList.push(data.params.name);
1577
1871
  }
1578
1872
  }
1873
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1579
1874
  if (isValidArray(config.blackList, 1)) {
1580
1875
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1581
1876
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1606,7 +1901,9 @@ export class Frontend extends EventEmitter {
1606
1901
  return;
1607
1902
  }
1608
1903
  const config = plugin.configJson;
1904
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1609
1905
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1906
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1610
1907
  if (select === 'serial')
1611
1908
  this.log.info(`Unselected device serial ${data.params.serial}`);
1612
1909
  if (select === 'name')
@@ -1615,6 +1912,7 @@ export class Frontend extends EventEmitter {
1615
1912
  if (config.postfix) {
1616
1913
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1617
1914
  }
1915
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1618
1916
  if (isValidArray(config.whiteList, 1)) {
1619
1917
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1620
1918
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1623,6 +1921,7 @@ export class Frontend extends EventEmitter {
1623
1921
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1624
1922
  }
1625
1923
  }
1924
+ // Add the serial to the blackList
1626
1925
  if (isValidArray(config.blackList)) {
1627
1926
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1628
1927
  config.blackList.push(data.params.serial);
@@ -1656,126 +1955,251 @@ export class Frontend extends EventEmitter {
1656
1955
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1657
1956
  }
1658
1957
  }
1958
+ /**
1959
+ * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1960
+ *
1961
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1962
+ * @param {string} time - The time string of the message
1963
+ * @param {string} name - The logger name of the message
1964
+ * @param {string} message - The content of the message.
1965
+ *
1966
+ * @remarks
1967
+ * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1968
+ * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1969
+ * The function sends the message to all connected clients.
1970
+ */
1659
1971
  wssSendMessage(level, time, name, message) {
1660
1972
  if (!level || !time || !name || !message)
1661
1973
  return;
1974
+ // Remove ANSI escape codes from the message
1975
+ // eslint-disable-next-line no-control-regex
1662
1976
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1977
+ // Remove leading asterisks from the message
1663
1978
  message = message.replace(/^\*+/, '');
1979
+ // Replace all occurrences of \t and \n
1664
1980
  message = message.replace(/[\t\n]/g, '');
1981
+ // Remove non-printable characters
1982
+ // eslint-disable-next-line no-control-regex
1665
1983
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1984
+ // Replace all occurrences of \" with "
1666
1985
  message = message.replace(/\\"/g, '"');
1986
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1667
1987
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1988
+ // Define the maximum allowed length for continuous characters without a space
1668
1989
  const maxContinuousLength = 100;
1669
1990
  const keepStartLength = 20;
1670
1991
  const keepEndLength = 20;
1992
+ // Split the message into words
1671
1993
  message = message
1672
1994
  .split(' ')
1673
1995
  .map((word) => {
1996
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1674
1997
  if (word.length > maxContinuousLength) {
1675
1998
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1676
1999
  }
1677
2000
  return word;
1678
2001
  })
1679
2002
  .join(' ');
2003
+ // Send the message to all connected clients
1680
2004
  this.webSocketServer?.clients.forEach((client) => {
1681
2005
  if (client.readyState === WebSocket.OPEN) {
1682
2006
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1683
2007
  }
1684
2008
  });
1685
2009
  }
2010
+ /**
2011
+ * Sends a need to refresh WebSocket message to all connected clients.
2012
+ *
2013
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
2014
+ * possible values:
2015
+ * - 'matterbridgeLatestVersion'
2016
+ * - 'matterbridgeDevVersion'
2017
+ * - 'matterbridgeAdvertise'
2018
+ * - 'online'
2019
+ * - 'offline'
2020
+ * - 'reachability'
2021
+ * - 'settings'
2022
+ * - 'plugins'
2023
+ * - 'pluginsRestart'
2024
+ * - 'devices'
2025
+ * - 'fabrics'
2026
+ * - 'sessions'
2027
+ */
1686
2028
  wssSendRefreshRequired(changed = null) {
1687
2029
  this.log.debug('Sending a refresh required message to all connected clients');
2030
+ // Send the message to all connected clients
1688
2031
  this.webSocketServer?.clients.forEach((client) => {
1689
2032
  if (client.readyState === WebSocket.OPEN) {
1690
2033
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1691
2034
  }
1692
2035
  });
1693
2036
  }
2037
+ /**
2038
+ * Sends a need to restart WebSocket message to all connected clients.
2039
+ *
2040
+ * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2041
+ * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2042
+ */
1694
2043
  wssSendRestartRequired(snackbar = true, fixed = false) {
1695
2044
  this.log.debug('Sending a restart required message to all connected clients');
1696
2045
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1697
2046
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1698
2047
  if (snackbar === true)
1699
2048
  this.wssSendSnackbarMessage(`Restart required`, 0);
2049
+ // Send the message to all connected clients
1700
2050
  this.webSocketServer?.clients.forEach((client) => {
1701
2051
  if (client.readyState === WebSocket.OPEN) {
1702
2052
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
1703
2053
  }
1704
2054
  });
1705
2055
  }
2056
+ /**
2057
+ * Sends a no need to restart WebSocket message to all connected clients.
2058
+ *
2059
+ * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
2060
+ */
1706
2061
  wssSendRestartNotRequired(snackbar = true) {
1707
2062
  this.log.debug('Sending a restart not required message to all connected clients');
1708
2063
  this.matterbridge.matterbridgeInformation.restartRequired = false;
1709
2064
  if (snackbar === true)
1710
2065
  this.wssSendCloseSnackbarMessage(`Restart required`);
2066
+ // Send the message to all connected clients
1711
2067
  this.webSocketServer?.clients.forEach((client) => {
1712
2068
  if (client.readyState === WebSocket.OPEN) {
1713
2069
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
1714
2070
  }
1715
2071
  });
1716
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.
2077
+ */
1717
2078
  wssSendUpdateRequired(devVersion = false) {
1718
2079
  this.log.debug('Sending an update required message to all connected clients');
1719
2080
  this.matterbridge.matterbridgeInformation.updateRequired = true;
2081
+ // Send the message to all connected clients
1720
2082
  this.webSocketServer?.clients.forEach((client) => {
1721
2083
  if (client.readyState === WebSocket.OPEN) {
1722
2084
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
1723
2085
  }
1724
2086
  });
1725
2087
  }
2088
+ /**
2089
+ * Sends a cpu update message to all connected clients.
2090
+ *
2091
+ * @param {number} cpuUsage - The CPU usage percentage to send.
2092
+ */
1726
2093
  wssSendCpuUpdate(cpuUsage) {
1727
2094
  if (hasParameter('debug'))
1728
2095
  this.log.debug('Sending a cpu update message to all connected clients');
2096
+ // Send the message to all connected clients
1729
2097
  this.webSocketServer?.clients.forEach((client) => {
1730
2098
  if (client.readyState === WebSocket.OPEN) {
1731
2099
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1732
2100
  }
1733
2101
  });
1734
2102
  }
2103
+ /**
2104
+ * Sends a memory update message to all connected clients.
2105
+ *
2106
+ * @param {string} totalMemory - The total memory in bytes.
2107
+ * @param {string} freeMemory - The free memory in bytes.
2108
+ * @param {string} rss - The resident set size in bytes.
2109
+ * @param {string} heapTotal - The total heap memory in bytes.
2110
+ * @param {string} heapUsed - The used heap memory in bytes.
2111
+ * @param {string} external - The external memory in bytes.
2112
+ * @param {string} arrayBuffers - The array buffers memory in bytes.
2113
+ */
1735
2114
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1736
2115
  if (hasParameter('debug'))
1737
2116
  this.log.debug('Sending a memory update message to all connected clients');
2117
+ // Send the message to all connected clients
1738
2118
  this.webSocketServer?.clients.forEach((client) => {
1739
2119
  if (client.readyState === WebSocket.OPEN) {
1740
2120
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1741
2121
  }
1742
2122
  });
1743
2123
  }
2124
+ /**
2125
+ * Sends an uptime update message to all connected clients.
2126
+ *
2127
+ * @param {string} systemUptime - The system uptime in a human-readable format.
2128
+ * @param {string} processUptime - The process uptime in a human-readable format.
2129
+ */
1744
2130
  wssSendUptimeUpdate(systemUptime, processUptime) {
1745
2131
  if (hasParameter('debug'))
1746
2132
  this.log.debug('Sending a uptime update message to all connected clients');
2133
+ // Send the message to all connected clients
1747
2134
  this.webSocketServer?.clients.forEach((client) => {
1748
2135
  if (client.readyState === WebSocket.OPEN) {
1749
2136
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1750
2137
  }
1751
2138
  });
1752
2139
  }
2140
+ /**
2141
+ * Sends an open snackbar message to all connected clients.
2142
+ *
2143
+ * @param {string} message - The message to send.
2144
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
2145
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2146
+ */
1753
2147
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1754
2148
  this.log.debug('Sending a snackbar message to all connected clients');
2149
+ // Send the message to all connected clients
1755
2150
  this.webSocketServer?.clients.forEach((client) => {
1756
2151
  if (client.readyState === WebSocket.OPEN) {
1757
2152
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1758
2153
  }
1759
2154
  });
1760
2155
  }
2156
+ /**
2157
+ * Sends a close snackbar message to all connected clients.
2158
+ *
2159
+ * @param {string} message - The message to send.
2160
+ */
1761
2161
  wssSendCloseSnackbarMessage(message) {
1762
2162
  this.log.debug('Sending a close snackbar message to all connected clients');
2163
+ // Send the message to all connected clients
1763
2164
  this.webSocketServer?.clients.forEach((client) => {
1764
2165
  if (client.readyState === WebSocket.OPEN) {
1765
2166
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1766
2167
  }
1767
2168
  });
1768
2169
  }
2170
+ /**
2171
+ * Sends an attribute update message to all connected WebSocket clients.
2172
+ *
2173
+ * @param {string | undefined} plugin - The name of the plugin.
2174
+ * @param {string | undefined} serialNumber - The serial number of the device.
2175
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
2176
+ * @param {string} cluster - The cluster name where the attribute belongs.
2177
+ * @param {string} attribute - The name of the attribute that changed.
2178
+ * @param {number | string | boolean} value - The new value of the attribute.
2179
+ *
2180
+ * @remarks
2181
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2182
+ * with the updated attribute information.
2183
+ */
1769
2184
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1770
2185
  this.log.debug('Sending an attribute update message to all connected clients');
2186
+ // Send the message to all connected clients
1771
2187
  this.webSocketServer?.clients.forEach((client) => {
1772
2188
  if (client.readyState === WebSocket.OPEN) {
1773
2189
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1774
2190
  }
1775
2191
  });
1776
2192
  }
2193
+ /**
2194
+ * Sends a message to all connected clients.
2195
+ *
2196
+ * @param {number} id - The message id.
2197
+ * @param {string} method - The message method.
2198
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
2199
+ */
1777
2200
  wssBroadcastMessage(id, method, params) {
1778
2201
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2202
+ // Send the message to all connected clients
1779
2203
  this.webSocketServer?.clients.forEach((client) => {
1780
2204
  if (client.readyState === WebSocket.OPEN) {
1781
2205
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1783,3 +2207,4 @@ export class Frontend extends EventEmitter {
1783
2207
  });
1784
2208
  }
1785
2209
  }
2210
+ //# sourceMappingURL=frontend.js.map