matterbridge 3.3.5 → 3.3.7-dev-20251102-c85d574

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 (309) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/broadcastServer.js +1 -92
  3. package/dist/broadcastServerTypes.js +0 -24
  4. package/dist/cli.js +1 -97
  5. package/dist/cliEmitter.js +0 -37
  6. package/dist/cliHistory.js +0 -38
  7. package/dist/clusters/export.js +0 -2
  8. package/dist/defaultConfigSchema.js +0 -24
  9. package/dist/deviceManager.js +1 -124
  10. package/dist/devices/airConditioner.js +0 -57
  11. package/dist/devices/batteryStorage.js +1 -48
  12. package/dist/devices/cooktop.js +0 -55
  13. package/dist/devices/dishwasher.js +0 -57
  14. package/dist/devices/evse.js +10 -74
  15. package/dist/devices/export.js +0 -5
  16. package/dist/devices/extractorHood.js +0 -42
  17. package/dist/devices/heatPump.js +2 -50
  18. package/dist/devices/laundryDryer.js +3 -62
  19. package/dist/devices/laundryWasher.js +4 -70
  20. package/dist/devices/microwaveOven.js +5 -88
  21. package/dist/devices/oven.js +0 -85
  22. package/dist/devices/refrigerator.js +0 -102
  23. package/dist/devices/roboticVacuumCleaner.js +9 -100
  24. package/dist/devices/solarPower.js +0 -38
  25. package/dist/devices/speaker.js +0 -84
  26. package/dist/devices/temperatureControl.js +3 -24
  27. package/dist/devices/waterHeater.js +2 -82
  28. package/dist/dgram/coap.js +13 -126
  29. package/dist/dgram/dgram.js +2 -114
  30. package/dist/dgram/mb_coap.js +3 -41
  31. package/dist/dgram/mb_mdns.js +15 -80
  32. package/dist/dgram/mdns.js +137 -299
  33. package/dist/dgram/multicast.js +1 -62
  34. package/dist/dgram/unicast.js +0 -54
  35. package/dist/frontend.js +34 -431
  36. package/dist/frontendTypes.js +0 -45
  37. package/dist/helpers.js +0 -53
  38. package/dist/index.js +0 -25
  39. package/dist/logger/export.js +0 -1
  40. package/dist/matter/behaviors.js +0 -2
  41. package/dist/matter/clusters.js +0 -2
  42. package/dist/matter/devices.js +0 -2
  43. package/dist/matter/endpoints.js +0 -2
  44. package/dist/matter/export.js +0 -3
  45. package/dist/matter/types.js +0 -3
  46. package/dist/matterbridge.js +46 -828
  47. package/dist/matterbridgeAccessoryPlatform.js +0 -37
  48. package/dist/matterbridgeBehaviors.js +5 -68
  49. package/dist/matterbridgeDeviceTypes.js +17 -638
  50. package/dist/matterbridgeDynamicPlatform.js +0 -37
  51. package/dist/matterbridgeEndpoint.js +52 -1408
  52. package/dist/matterbridgeEndpointHelpers.js +19 -464
  53. package/dist/matterbridgePlatform.js +1 -341
  54. package/dist/matterbridgeTypes.js +0 -26
  55. package/dist/pluginManager.js +3 -319
  56. package/dist/shelly.js +7 -168
  57. package/dist/storage/export.js +0 -1
  58. package/dist/update.js +0 -69
  59. package/dist/utils/colorUtils.js +2 -97
  60. package/dist/utils/commandLine.js +0 -60
  61. package/dist/utils/copyDirectory.js +1 -38
  62. package/dist/utils/createDirectory.js +0 -33
  63. package/dist/utils/createZip.js +2 -47
  64. package/dist/utils/deepCopy.js +0 -39
  65. package/dist/utils/deepEqual.js +1 -72
  66. package/dist/utils/error.js +0 -41
  67. package/dist/utils/export.js +0 -1
  68. package/dist/utils/format.js +0 -49
  69. package/dist/utils/hex.js +0 -124
  70. package/dist/utils/inspector.js +1 -69
  71. package/dist/utils/isvalid.js +0 -101
  72. package/dist/utils/jestHelpers.js +3 -153
  73. package/dist/utils/network.js +5 -96
  74. package/dist/utils/spawn.js +0 -71
  75. package/dist/utils/tracker.js +1 -64
  76. package/dist/utils/wait.js +8 -60
  77. package/frontend/build/assets/index.css +1 -1
  78. package/frontend/build/assets/index.js +4 -4
  79. package/frontend/build/assets/vendor_mui.js +10 -10
  80. package/frontend/build/assets/vendor_node_modules.js +30 -29
  81. package/frontend/package-lock.json +727 -732
  82. package/frontend/package.json +22 -22
  83. package/npm-shrinkwrap.json +2 -2
  84. package/package.json +1 -2
  85. package/dist/broadcastServer.d.ts +0 -112
  86. package/dist/broadcastServer.d.ts.map +0 -1
  87. package/dist/broadcastServer.js.map +0 -1
  88. package/dist/broadcastServerTypes.d.ts +0 -803
  89. package/dist/broadcastServerTypes.d.ts.map +0 -1
  90. package/dist/broadcastServerTypes.js.map +0 -1
  91. package/dist/cli.d.ts +0 -30
  92. package/dist/cli.d.ts.map +0 -1
  93. package/dist/cli.js.map +0 -1
  94. package/dist/cliEmitter.d.ts +0 -50
  95. package/dist/cliEmitter.d.ts.map +0 -1
  96. package/dist/cliEmitter.js.map +0 -1
  97. package/dist/cliHistory.d.ts +0 -48
  98. package/dist/cliHistory.d.ts.map +0 -1
  99. package/dist/cliHistory.js.map +0 -1
  100. package/dist/clusters/export.d.ts +0 -2
  101. package/dist/clusters/export.d.ts.map +0 -1
  102. package/dist/clusters/export.js.map +0 -1
  103. package/dist/defaultConfigSchema.d.ts +0 -28
  104. package/dist/defaultConfigSchema.d.ts.map +0 -1
  105. package/dist/defaultConfigSchema.js.map +0 -1
  106. package/dist/deviceManager.d.ts +0 -117
  107. package/dist/deviceManager.d.ts.map +0 -1
  108. package/dist/deviceManager.js.map +0 -1
  109. package/dist/devices/airConditioner.d.ts +0 -98
  110. package/dist/devices/airConditioner.d.ts.map +0 -1
  111. package/dist/devices/airConditioner.js.map +0 -1
  112. package/dist/devices/batteryStorage.d.ts +0 -48
  113. package/dist/devices/batteryStorage.d.ts.map +0 -1
  114. package/dist/devices/batteryStorage.js.map +0 -1
  115. package/dist/devices/cooktop.d.ts +0 -60
  116. package/dist/devices/cooktop.d.ts.map +0 -1
  117. package/dist/devices/cooktop.js.map +0 -1
  118. package/dist/devices/dishwasher.d.ts +0 -71
  119. package/dist/devices/dishwasher.d.ts.map +0 -1
  120. package/dist/devices/dishwasher.js.map +0 -1
  121. package/dist/devices/evse.d.ts +0 -76
  122. package/dist/devices/evse.d.ts.map +0 -1
  123. package/dist/devices/evse.js.map +0 -1
  124. package/dist/devices/export.d.ts +0 -17
  125. package/dist/devices/export.d.ts.map +0 -1
  126. package/dist/devices/export.js.map +0 -1
  127. package/dist/devices/extractorHood.d.ts +0 -46
  128. package/dist/devices/extractorHood.d.ts.map +0 -1
  129. package/dist/devices/extractorHood.js.map +0 -1
  130. package/dist/devices/heatPump.d.ts +0 -47
  131. package/dist/devices/heatPump.d.ts.map +0 -1
  132. package/dist/devices/heatPump.js.map +0 -1
  133. package/dist/devices/laundryDryer.d.ts +0 -67
  134. package/dist/devices/laundryDryer.d.ts.map +0 -1
  135. package/dist/devices/laundryDryer.js.map +0 -1
  136. package/dist/devices/laundryWasher.d.ts +0 -81
  137. package/dist/devices/laundryWasher.d.ts.map +0 -1
  138. package/dist/devices/laundryWasher.js.map +0 -1
  139. package/dist/devices/microwaveOven.d.ts +0 -168
  140. package/dist/devices/microwaveOven.d.ts.map +0 -1
  141. package/dist/devices/microwaveOven.js.map +0 -1
  142. package/dist/devices/oven.d.ts +0 -105
  143. package/dist/devices/oven.d.ts.map +0 -1
  144. package/dist/devices/oven.js.map +0 -1
  145. package/dist/devices/refrigerator.d.ts +0 -118
  146. package/dist/devices/refrigerator.d.ts.map +0 -1
  147. package/dist/devices/refrigerator.js.map +0 -1
  148. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  149. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  150. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  151. package/dist/devices/solarPower.d.ts +0 -40
  152. package/dist/devices/solarPower.d.ts.map +0 -1
  153. package/dist/devices/solarPower.js.map +0 -1
  154. package/dist/devices/speaker.d.ts +0 -87
  155. package/dist/devices/speaker.d.ts.map +0 -1
  156. package/dist/devices/speaker.js.map +0 -1
  157. package/dist/devices/temperatureControl.d.ts +0 -166
  158. package/dist/devices/temperatureControl.d.ts.map +0 -1
  159. package/dist/devices/temperatureControl.js.map +0 -1
  160. package/dist/devices/waterHeater.d.ts +0 -111
  161. package/dist/devices/waterHeater.d.ts.map +0 -1
  162. package/dist/devices/waterHeater.js.map +0 -1
  163. package/dist/dgram/coap.d.ts +0 -205
  164. package/dist/dgram/coap.d.ts.map +0 -1
  165. package/dist/dgram/coap.js.map +0 -1
  166. package/dist/dgram/dgram.d.ts +0 -141
  167. package/dist/dgram/dgram.d.ts.map +0 -1
  168. package/dist/dgram/dgram.js.map +0 -1
  169. package/dist/dgram/mb_coap.d.ts +0 -24
  170. package/dist/dgram/mb_coap.d.ts.map +0 -1
  171. package/dist/dgram/mb_coap.js.map +0 -1
  172. package/dist/dgram/mb_mdns.d.ts +0 -24
  173. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  174. package/dist/dgram/mb_mdns.js.map +0 -1
  175. package/dist/dgram/mdns.d.ts +0 -290
  176. package/dist/dgram/mdns.d.ts.map +0 -1
  177. package/dist/dgram/mdns.js.map +0 -1
  178. package/dist/dgram/multicast.d.ts +0 -67
  179. package/dist/dgram/multicast.d.ts.map +0 -1
  180. package/dist/dgram/multicast.js.map +0 -1
  181. package/dist/dgram/unicast.d.ts +0 -56
  182. package/dist/dgram/unicast.d.ts.map +0 -1
  183. package/dist/dgram/unicast.js.map +0 -1
  184. package/dist/frontend.d.ts +0 -236
  185. package/dist/frontend.d.ts.map +0 -1
  186. package/dist/frontend.js.map +0 -1
  187. package/dist/frontendTypes.d.ts +0 -529
  188. package/dist/frontendTypes.d.ts.map +0 -1
  189. package/dist/frontendTypes.js.map +0 -1
  190. package/dist/helpers.d.ts +0 -48
  191. package/dist/helpers.d.ts.map +0 -1
  192. package/dist/helpers.js.map +0 -1
  193. package/dist/index.d.ts +0 -33
  194. package/dist/index.d.ts.map +0 -1
  195. package/dist/index.js.map +0 -1
  196. package/dist/logger/export.d.ts +0 -2
  197. package/dist/logger/export.d.ts.map +0 -1
  198. package/dist/logger/export.js.map +0 -1
  199. package/dist/matter/behaviors.d.ts +0 -2
  200. package/dist/matter/behaviors.d.ts.map +0 -1
  201. package/dist/matter/behaviors.js.map +0 -1
  202. package/dist/matter/clusters.d.ts +0 -2
  203. package/dist/matter/clusters.d.ts.map +0 -1
  204. package/dist/matter/clusters.js.map +0 -1
  205. package/dist/matter/devices.d.ts +0 -2
  206. package/dist/matter/devices.d.ts.map +0 -1
  207. package/dist/matter/devices.js.map +0 -1
  208. package/dist/matter/endpoints.d.ts +0 -2
  209. package/dist/matter/endpoints.d.ts.map +0 -1
  210. package/dist/matter/endpoints.js.map +0 -1
  211. package/dist/matter/export.d.ts +0 -5
  212. package/dist/matter/export.d.ts.map +0 -1
  213. package/dist/matter/export.js.map +0 -1
  214. package/dist/matter/types.d.ts +0 -3
  215. package/dist/matter/types.d.ts.map +0 -1
  216. package/dist/matter/types.js.map +0 -1
  217. package/dist/matterbridge.d.ts +0 -476
  218. package/dist/matterbridge.d.ts.map +0 -1
  219. package/dist/matterbridge.js.map +0 -1
  220. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  221. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  222. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  223. package/dist/matterbridgeBehaviors.d.ts +0 -2404
  224. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  225. package/dist/matterbridgeBehaviors.js.map +0 -1
  226. package/dist/matterbridgeDeviceTypes.d.ts +0 -770
  227. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  228. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  229. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  230. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  231. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  232. package/dist/matterbridgeEndpoint.d.ts +0 -1556
  233. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  234. package/dist/matterbridgeEndpoint.js.map +0 -1
  235. package/dist/matterbridgeEndpointHelpers.d.ts +0 -758
  236. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  237. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  238. package/dist/matterbridgePlatform.d.ts +0 -402
  239. package/dist/matterbridgePlatform.d.ts.map +0 -1
  240. package/dist/matterbridgePlatform.js.map +0 -1
  241. package/dist/matterbridgeTypes.d.ts +0 -226
  242. package/dist/matterbridgeTypes.d.ts.map +0 -1
  243. package/dist/matterbridgeTypes.js.map +0 -1
  244. package/dist/pluginManager.d.ts +0 -347
  245. package/dist/pluginManager.d.ts.map +0 -1
  246. package/dist/pluginManager.js.map +0 -1
  247. package/dist/shelly.d.ts +0 -174
  248. package/dist/shelly.d.ts.map +0 -1
  249. package/dist/shelly.js.map +0 -1
  250. package/dist/storage/export.d.ts +0 -2
  251. package/dist/storage/export.d.ts.map +0 -1
  252. package/dist/storage/export.js.map +0 -1
  253. package/dist/update.d.ts +0 -75
  254. package/dist/update.d.ts.map +0 -1
  255. package/dist/update.js.map +0 -1
  256. package/dist/utils/colorUtils.d.ts +0 -101
  257. package/dist/utils/colorUtils.d.ts.map +0 -1
  258. package/dist/utils/colorUtils.js.map +0 -1
  259. package/dist/utils/commandLine.d.ts +0 -66
  260. package/dist/utils/commandLine.d.ts.map +0 -1
  261. package/dist/utils/commandLine.js.map +0 -1
  262. package/dist/utils/copyDirectory.d.ts +0 -33
  263. package/dist/utils/copyDirectory.d.ts.map +0 -1
  264. package/dist/utils/copyDirectory.js.map +0 -1
  265. package/dist/utils/createDirectory.d.ts +0 -34
  266. package/dist/utils/createDirectory.d.ts.map +0 -1
  267. package/dist/utils/createDirectory.js.map +0 -1
  268. package/dist/utils/createZip.d.ts +0 -39
  269. package/dist/utils/createZip.d.ts.map +0 -1
  270. package/dist/utils/createZip.js.map +0 -1
  271. package/dist/utils/deepCopy.d.ts +0 -32
  272. package/dist/utils/deepCopy.d.ts.map +0 -1
  273. package/dist/utils/deepCopy.js.map +0 -1
  274. package/dist/utils/deepEqual.d.ts +0 -54
  275. package/dist/utils/deepEqual.d.ts.map +0 -1
  276. package/dist/utils/deepEqual.js.map +0 -1
  277. package/dist/utils/error.d.ts +0 -44
  278. package/dist/utils/error.d.ts.map +0 -1
  279. package/dist/utils/error.js.map +0 -1
  280. package/dist/utils/export.d.ts +0 -13
  281. package/dist/utils/export.d.ts.map +0 -1
  282. package/dist/utils/export.js.map +0 -1
  283. package/dist/utils/format.d.ts +0 -53
  284. package/dist/utils/format.d.ts.map +0 -1
  285. package/dist/utils/format.js.map +0 -1
  286. package/dist/utils/hex.d.ts +0 -89
  287. package/dist/utils/hex.d.ts.map +0 -1
  288. package/dist/utils/hex.js.map +0 -1
  289. package/dist/utils/inspector.d.ts +0 -87
  290. package/dist/utils/inspector.d.ts.map +0 -1
  291. package/dist/utils/inspector.js.map +0 -1
  292. package/dist/utils/isvalid.d.ts +0 -103
  293. package/dist/utils/isvalid.d.ts.map +0 -1
  294. package/dist/utils/isvalid.js.map +0 -1
  295. package/dist/utils/jestHelpers.d.ts +0 -139
  296. package/dist/utils/jestHelpers.d.ts.map +0 -1
  297. package/dist/utils/jestHelpers.js.map +0 -1
  298. package/dist/utils/network.d.ts +0 -101
  299. package/dist/utils/network.d.ts.map +0 -1
  300. package/dist/utils/network.js.map +0 -1
  301. package/dist/utils/spawn.d.ts +0 -35
  302. package/dist/utils/spawn.d.ts.map +0 -1
  303. package/dist/utils/spawn.js.map +0 -1
  304. package/dist/utils/tracker.d.ts +0 -108
  305. package/dist/utils/tracker.d.ts.map +0 -1
  306. package/dist/utils/tracker.js.map +0 -1
  307. package/dist/utils/wait.d.ts +0 -54
  308. package/dist/utils/wait.d.ts.map +0 -1
  309. package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js CHANGED
@@ -1,34 +1,8 @@
1
- /**
2
- * This file contains the class Frontend.
3
- *
4
- * @file frontend.ts
5
- * @author Luca Liguori
6
- * @created 2025-01-13
7
- * @version 1.3.0
8
- * @license Apache-2.0
9
- *
10
- * Copyright 2025, 2026, 2027 Luca Liguori.
11
- *
12
- * Licensed under the Apache License, Version 2.0 (the "License");
13
- * you may not use this file except in compliance with the License.
14
- * You may obtain a copy of the License at
15
- *
16
- * http://www.apache.org/licenses/LICENSE-2.0
17
- *
18
- * Unless required by applicable law or agreed to in writing, software
19
- * distributed under the License is distributed on an "AS IS" BASIS,
20
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- * See the License for the specific language governing permissions and
22
- * limitations under the License.
23
- */
24
- // eslint-disable-next-line no-console
25
1
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
26
2
  console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
27
- // Node modules
28
3
  import os from 'node:os';
29
4
  import path from 'node:path';
30
5
  import EventEmitter from 'node:events';
31
- // AnsiLogger module
32
6
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
33
7
  import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
34
8
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
@@ -61,7 +35,7 @@ export class Frontend extends EventEmitter {
61
35
  constructor(matterbridge) {
62
36
  super();
63
37
  this.matterbridge = matterbridge;
64
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
38
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
65
39
  this.log.logNameColor = '\x1b[38;5;97m';
66
40
  this.server = new BroadcastServer('frontend', this.log);
67
41
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -156,53 +130,23 @@ export class Frontend extends EventEmitter {
156
130
  this.port = port;
157
131
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
158
132
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
159
- // Initialize multer with the upload directory
160
133
  const multer = await import('multer');
161
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
134
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
162
135
  const upload = multer.default({ dest: uploadDir });
163
- // Create the express app that serves the frontend
164
136
  const express = await import('express');
165
137
  this.expressApp = express.default();
166
- // Inject logging/debug wrapper for route/middleware registration
167
- /*
168
- const methods = ['get', 'post', 'put', 'delete', 'use'];
169
- for (const method of methods) {
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
- const original = (this.expressApp as any)[method].bind(this.expressApp);
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
174
- try {
175
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
176
- return original(path, ...rest);
177
- } catch (err) {
178
- console.error(`[ERROR] Failed to register route: ${path}`);
179
- throw err;
180
- }
181
- };
182
- }
183
- */
184
- // Log all requests to the server for debugging
185
- /*
186
- this.expressApp.use((req, res, next) => {
187
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
188
- next();
189
- });
190
- */
191
- // Serve static files from 'frontend/build' directory
192
138
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
193
- // Create a WebSocket server and attach it to the http or https server
194
139
  this.log.debug(`Creating WebSocketServer...`);
195
140
  const ws = await import('ws');
196
141
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
197
142
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
198
143
  this.webSocketServer.on('connection', (ws, request) => {
199
144
  const clientIp = request.socket.remoteAddress;
200
- // Set the global logger callback for the WebSocketServer
201
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
202
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
203
- callbackLogLevel = "info" /* LogLevel.INFO */;
204
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
205
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
145
+ let callbackLogLevel = "notice";
146
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
147
+ callbackLogLevel = "info";
148
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
149
+ callbackLogLevel = "debug";
206
150
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
207
151
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
208
152
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -224,25 +168,16 @@ export class Frontend extends EventEmitter {
224
168
  }
225
169
  });
226
170
  ws.on('error', (error) => {
227
- // istanbul ignore next
228
171
  this.log.error(`WebSocket client error: ${error}`);
229
172
  });
230
173
  });
231
174
  this.webSocketServer.on('close', () => {
232
175
  this.log.debug(`WebSocketServer closed`);
233
176
  });
234
- /* With { noServer: true } it never fires
235
- this.webSocketServer.on('listening', () => {
236
- this.log.info(`The WebSocketServer is listening`);
237
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
238
- });
239
- */
240
- // istanbul ignore next
241
177
  this.webSocketServer.on('error', (ws, error) => {
242
178
  this.log.error(`WebSocketServer error: ${error}`);
243
179
  });
244
180
  if (!hasParameter('ssl')) {
245
- // Create an HTTP server and attach the express app
246
181
  const http = await import('node:http');
247
182
  try {
248
183
  this.log.debug(`Creating HTTP server...`);
@@ -253,7 +188,6 @@ export class Frontend extends EventEmitter {
253
188
  this.emit('server_error', error);
254
189
  return;
255
190
  }
256
- // Listen on the specified port
257
191
  if (hasParameter('ingress')) {
258
192
  this.httpServer.listen(this.port, '0.0.0.0', () => {
259
193
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -273,29 +207,23 @@ export class Frontend extends EventEmitter {
273
207
  }
274
208
  this.httpServer.on('upgrade', async (req, socket, head) => {
275
209
  try {
276
- // Only proceed for real WebSocket upgrades
277
- // istanbul ignore next cause is only a safety check
278
210
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
279
211
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
280
212
  return socket.destroy();
281
213
  }
282
- // Build a URL so we can read ?password=...
283
214
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
284
- // Validate WebSocket password
285
215
  const password = url.searchParams.get('password') ?? '';
286
216
  if (password !== this.storedPassword) {
287
217
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
288
218
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
289
219
  return socket.destroy();
290
220
  }
291
- // Complete the WebSocket handshake
292
221
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
293
222
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
294
223
  this.webSocketServer?.emit('connection', ws, req);
295
224
  });
296
225
  }
297
226
  catch (err) {
298
- /* istanbul ignore next: only triggered on unexpected internal error */
299
227
  {
300
228
  inspectError(this.log, 'WebSocket upgrade error:', err);
301
229
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -318,7 +246,6 @@ export class Frontend extends EventEmitter {
318
246
  });
319
247
  }
320
248
  else {
321
- // SSL is enabled, load the certificate and the private key
322
249
  let cert;
323
250
  let key;
324
251
  let ca;
@@ -328,7 +255,6 @@ export class Frontend extends EventEmitter {
328
255
  let httpsServerOptions = {};
329
256
  const fs = await import('node:fs');
330
257
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
331
- // Load the p12 certificate and the passphrase
332
258
  try {
333
259
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
334
260
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -340,7 +266,7 @@ export class Frontend extends EventEmitter {
340
266
  }
341
267
  try {
342
268
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
343
- passphrase = passphrase.trim(); // Ensure no extra characters
269
+ passphrase = passphrase.trim();
344
270
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
345
271
  }
346
272
  catch (error) {
@@ -351,7 +277,6 @@ export class Frontend extends EventEmitter {
351
277
  httpsServerOptions = { pfx, passphrase };
352
278
  }
353
279
  else {
354
- // 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.
355
280
  try {
356
281
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
357
282
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -381,10 +306,9 @@ export class Frontend extends EventEmitter {
381
306
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
382
307
  }
383
308
  if (hasParameter('mtls')) {
384
- httpsServerOptions.requestCert = true; // Request client certificate
385
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
309
+ httpsServerOptions.requestCert = true;
310
+ httpsServerOptions.rejectUnauthorized = true;
386
311
  }
387
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
388
312
  const https = await import('node:https');
389
313
  try {
390
314
  this.log.debug(`Creating HTTPS server...`);
@@ -395,7 +319,6 @@ export class Frontend extends EventEmitter {
395
319
  this.emit('server_error', error);
396
320
  return;
397
321
  }
398
- // Listen on the specified port
399
322
  if (hasParameter('ingress')) {
400
323
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
401
324
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -415,29 +338,23 @@ export class Frontend extends EventEmitter {
415
338
  }
416
339
  this.httpsServer.on('upgrade', async (req, socket, head) => {
417
340
  try {
418
- // Only proceed for real WebSocket upgrades
419
- // istanbul ignore next cause is only a safety check
420
341
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
421
342
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
422
343
  return socket.destroy();
423
344
  }
424
- // Build a URL so we can read ?password=...
425
345
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
426
- // Validate WebSocket password
427
346
  const password = url.searchParams.get('password') ?? '';
428
347
  if (password !== this.storedPassword) {
429
348
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
430
349
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
431
350
  return socket.destroy();
432
351
  }
433
- // Complete the WebSocket handshake
434
352
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
435
353
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
436
354
  this.webSocketServer?.emit('connection', ws, req);
437
355
  });
438
356
  }
439
357
  catch (err) {
440
- /* istanbul ignore next: only triggered on unexpected internal error */
441
358
  {
442
359
  inspectError(this.log, 'WebSocket upgrade error:', err);
443
360
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -459,7 +376,6 @@ export class Frontend extends EventEmitter {
459
376
  return;
460
377
  });
461
378
  }
462
- // Subscribe to cli events
463
379
  cliEmitter.removeAllListeners();
464
380
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
465
381
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -470,8 +386,6 @@ export class Frontend extends EventEmitter {
470
386
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
471
387
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
472
388
  });
473
- // Endpoint to validate login code
474
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
475
389
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
476
390
  const { password } = req.body;
477
391
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -484,20 +398,17 @@ export class Frontend extends EventEmitter {
484
398
  res.json({ valid: false });
485
399
  }
486
400
  });
487
- // Endpoint to provide health check for docker
488
401
  this.expressApp.get('/health', (req, res) => {
489
402
  this.log.debug('Express received /health');
490
403
  const healthStatus = {
491
- status: 'ok', // Indicate service is healthy
492
- uptime: process.uptime(), // Server uptime in seconds
493
- timestamp: new Date().toISOString(), // Current timestamp
404
+ status: 'ok',
405
+ uptime: process.uptime(),
406
+ timestamp: new Date().toISOString(),
494
407
  };
495
408
  res.status(200).json(healthStatus);
496
409
  });
497
- // Endpoint to provide memory usage details
498
410
  this.expressApp.get('/memory', async (req, res) => {
499
411
  this.log.debug('Express received /memory');
500
- // Memory usage from process
501
412
  const memoryUsageRaw = process.memoryUsage();
502
413
  const memoryUsage = {
503
414
  rss: formatBytes(memoryUsageRaw.rss),
@@ -506,13 +417,10 @@ export class Frontend extends EventEmitter {
506
417
  external: formatBytes(memoryUsageRaw.external),
507
418
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
508
419
  };
509
- // V8 heap statistics
510
420
  const { default: v8 } = await import('node:v8');
511
421
  const heapStatsRaw = v8.getHeapStatistics();
512
422
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
513
- // Format heapStats
514
423
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
515
- // Format heapSpaces
516
424
  const heapSpaces = heapSpacesRaw.map((space) => ({
517
425
  ...space,
518
426
  space_size: formatBytes(space.space_size),
@@ -531,22 +439,18 @@ export class Frontend extends EventEmitter {
531
439
  };
532
440
  res.status(200).json(memoryReport);
533
441
  });
534
- // Endpoint to provide settings
535
442
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
536
443
  this.log.debug('The frontend sent /api/settings');
537
444
  res.json(await this.getApiSettings());
538
445
  });
539
- // Endpoint to provide plugins
540
446
  this.expressApp.get('/api/plugins', async (req, res) => {
541
447
  this.log.debug('The frontend sent /api/plugins');
542
448
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
543
449
  });
544
- // Endpoint to provide devices
545
450
  this.expressApp.get('/api/devices', async (req, res) => {
546
451
  this.log.debug('The frontend sent /api/devices');
547
452
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
548
453
  });
549
- // Endpoint to view the matterbridge log
550
454
  this.expressApp.get('/api/view-mblog', async (req, res) => {
551
455
  this.log.debug('The frontend sent /api/view-mblog');
552
456
  try {
@@ -560,7 +464,6 @@ export class Frontend extends EventEmitter {
560
464
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
561
465
  }
562
466
  });
563
- // Endpoint to view the matter.js log
564
467
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
565
468
  this.log.debug('The frontend sent /api/view-mjlog');
566
469
  try {
@@ -574,7 +477,6 @@ export class Frontend extends EventEmitter {
574
477
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
575
478
  }
576
479
  });
577
- // Endpoint to view the diagnostic.log
578
480
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
579
481
  this.log.debug('The frontend sent /api/view-diagnostic');
580
482
  await this.generateDiagnostic();
@@ -585,13 +487,10 @@ export class Frontend extends EventEmitter {
585
487
  res.send(data.slice(29));
586
488
  }
587
489
  catch (error) {
588
- // istanbul ignore next
589
490
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
590
- // istanbul ignore next
591
491
  res.status(500).send('Error reading diagnostic log file.');
592
492
  }
593
493
  });
594
- // Endpoint to download the diagnostic.log
595
494
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
596
495
  this.log.debug(`The frontend sent /api/download-diagnostic`);
597
496
  await this.generateDiagnostic();
@@ -602,19 +501,16 @@ export class Frontend extends EventEmitter {
602
501
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
603
502
  }
604
503
  catch (error) {
605
- // istanbul ignore next
606
504
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
607
505
  }
608
506
  res.type('text/plain');
609
507
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
610
- /* istanbul ignore if */
611
508
  if (error) {
612
509
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
613
510
  res.status(500).send('Error downloading the diagnostic log file');
614
511
  }
615
512
  });
616
513
  });
617
- // Endpoint to view the history.html
618
514
  this.expressApp.get('/api/viewhistory', async (req, res) => {
619
515
  this.log.debug('The frontend sent /api/viewhistory');
620
516
  try {
@@ -628,7 +524,6 @@ export class Frontend extends EventEmitter {
628
524
  res.status(500).send('Error reading history file.');
629
525
  }
630
526
  });
631
- // Endpoint to download the history.html
632
527
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
633
528
  this.log.debug(`The frontend sent /api/downloadhistory`);
634
529
  try {
@@ -638,7 +533,6 @@ export class Frontend extends EventEmitter {
638
533
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
639
534
  res.type('text/plain');
640
535
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
641
- /* istanbul ignore if */
642
536
  if (error) {
643
537
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
644
538
  res.status(500).send('Error downloading history file');
@@ -650,7 +544,6 @@ export class Frontend extends EventEmitter {
650
544
  res.status(500).send('Error reading history file.');
651
545
  }
652
546
  });
653
- // Endpoint to view the shelly log
654
547
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
655
548
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
656
549
  try {
@@ -664,7 +557,6 @@ export class Frontend extends EventEmitter {
664
557
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
665
558
  }
666
559
  });
667
- // Endpoint to download the matterbridge log
668
560
  this.expressApp.get('/api/download-mblog', async (req, res) => {
669
561
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
670
562
  const fs = await import('node:fs');
@@ -679,14 +571,12 @@ export class Frontend extends EventEmitter {
679
571
  }
680
572
  res.type('text/plain');
681
573
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
682
- /* istanbul ignore if */
683
574
  if (error) {
684
575
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
685
576
  res.status(500).send('Error downloading the matterbridge log file');
686
577
  }
687
578
  });
688
579
  });
689
- // Endpoint to download the matter log
690
580
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
691
581
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
692
582
  const fs = await import('node:fs');
@@ -701,14 +591,12 @@ export class Frontend extends EventEmitter {
701
591
  }
702
592
  res.type('text/plain');
703
593
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
704
- /* istanbul ignore if */
705
594
  if (error) {
706
595
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
707
596
  res.status(500).send('Error downloading the matter log file');
708
597
  }
709
598
  });
710
599
  });
711
- // Endpoint to download the shelly log
712
600
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
713
601
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
714
602
  const fs = await import('node:fs');
@@ -723,91 +611,75 @@ export class Frontend extends EventEmitter {
723
611
  }
724
612
  res.type('text/plain');
725
613
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
726
- /* istanbul ignore if */
727
614
  if (error) {
728
615
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
729
616
  res.status(500).send('Error downloading Shelly system log file');
730
617
  }
731
618
  });
732
619
  });
733
- // Endpoint to download the matterbridge storage directory
734
620
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
735
621
  this.log.debug('The frontend sent /api/download-mbstorage');
736
622
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
737
623
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
738
- /* istanbul ignore if */
739
624
  if (error) {
740
625
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
741
626
  res.status(500).send('Error downloading the matterbridge storage file');
742
627
  }
743
628
  });
744
629
  });
745
- // Endpoint to download the matter storage file
746
630
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
747
631
  this.log.debug('The frontend sent /api/download-mjstorage');
748
632
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
749
633
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
750
- /* istanbul ignore if */
751
634
  if (error) {
752
635
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
753
636
  res.status(500).send('Error downloading the matter storage zip file');
754
637
  }
755
638
  });
756
639
  });
757
- // Endpoint to download the matterbridge plugin directory
758
640
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
759
641
  this.log.debug('The frontend sent /api/download-pluginstorage');
760
642
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
761
643
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
762
- /* istanbul ignore if */
763
644
  if (error) {
764
645
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
765
646
  res.status(500).send('Error downloading the matterbridge plugin storage file');
766
647
  }
767
648
  });
768
649
  });
769
- // Endpoint to download the matterbridge plugin config files
770
650
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
771
651
  this.log.debug('The frontend sent /api/download-pluginconfig');
772
652
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
773
653
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
774
- /* istanbul ignore if */
775
654
  if (error) {
776
655
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
777
656
  res.status(500).send('Error downloading the matterbridge plugin config file');
778
657
  }
779
658
  });
780
659
  });
781
- // Endpoint to download the matterbridge backup (created with the backup command)
782
660
  this.expressApp.get('/api/download-backup', async (req, res) => {
783
661
  this.log.debug('The frontend sent /api/download-backup');
784
662
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
785
- /* istanbul ignore if */
786
663
  if (error) {
787
664
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
788
665
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
789
666
  }
790
667
  });
791
668
  });
792
- // Endpoint to upload a package
793
669
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
794
670
  const { filename } = req.body;
795
671
  const file = req.file;
796
- /* istanbul ignore if */
797
672
  if (!file || !filename) {
798
673
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
799
674
  res.status(400).send('Invalid request: file and filename are required');
800
675
  return;
801
676
  }
802
677
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
803
- // Define the path where the plugin file will be saved
804
678
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
805
679
  try {
806
- // Move the uploaded file to the specified path
807
680
  const fs = await import('node:fs');
808
681
  await fs.promises.rename(file.path, filePath);
809
682
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
810
- // Install the plugin package
811
683
  if (filename.endsWith('.tgz')) {
812
684
  const { spawnCommand } = await import('./utils/spawn.js');
813
685
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
@@ -827,7 +699,6 @@ export class Frontend extends EventEmitter {
827
699
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
828
700
  }
829
701
  });
830
- // Fallback for routing (must be the last route)
831
702
  this.expressApp.use((req, res) => {
832
703
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
833
704
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -837,16 +708,13 @@ export class Frontend extends EventEmitter {
837
708
  async stop() {
838
709
  this.log.debug('Stopping the frontend...');
839
710
  const ws = await import('ws');
840
- // Remove listeners from the express app
841
711
  if (this.expressApp) {
842
712
  this.expressApp.removeAllListeners();
843
713
  this.expressApp = undefined;
844
714
  this.log.debug('Frontend app closed successfully');
845
715
  }
846
- // Close the WebSocket server
847
716
  if (this.webSocketServer) {
848
717
  this.log.debug('Closing WebSocket server...');
849
- // Close all active connections
850
718
  this.webSocketServer.clients.forEach((client) => {
851
719
  if (client.readyState === ws.WebSocket.OPEN) {
852
720
  client.close();
@@ -855,7 +723,6 @@ export class Frontend extends EventEmitter {
855
723
  await withTimeout(new Promise((resolve) => {
856
724
  this.webSocketServer?.close((error) => {
857
725
  if (error) {
858
- // istanbul ignore next
859
726
  this.log.error(`Error closing WebSocket server: ${error}`);
860
727
  }
861
728
  else {
@@ -868,27 +735,8 @@ export class Frontend extends EventEmitter {
868
735
  this.webSocketServer.removeAllListeners();
869
736
  this.webSocketServer = undefined;
870
737
  }
871
- // Close the http server
872
738
  if (this.httpServer) {
873
739
  this.log.debug('Closing http server...');
874
- /*
875
- await withTimeout(
876
- new Promise<void>((resolve) => {
877
- this.httpServer?.close((error) => {
878
- if (error) {
879
- // istanbul ignore next
880
- this.log.error(`Error closing http server: ${error}`);
881
- } else {
882
- this.log.debug('Http server closed successfully');
883
- this.emit('server_stopped');
884
- }
885
- resolve();
886
- });
887
- }),
888
- 5000,
889
- false,
890
- );
891
- */
892
740
  this.httpServer.close();
893
741
  this.log.debug('Http server closed successfully');
894
742
  this.listening = false;
@@ -897,27 +745,8 @@ export class Frontend extends EventEmitter {
897
745
  this.httpServer = undefined;
898
746
  this.log.debug('Frontend http server closed successfully');
899
747
  }
900
- // Close the https server
901
748
  if (this.httpsServer) {
902
749
  this.log.debug('Closing https server...');
903
- /*
904
- await withTimeout(
905
- new Promise<void>((resolve) => {
906
- this.httpsServer?.close((error) => {
907
- if (error) {
908
- // istanbul ignore next
909
- this.log.error(`Error closing https server: ${error}`);
910
- } else {
911
- this.log.debug('Https server closed successfully');
912
- this.emit('server_stopped');
913
- }
914
- resolve();
915
- });
916
- }),
917
- 5000,
918
- false,
919
- );
920
- */
921
750
  this.httpsServer.close();
922
751
  this.log.debug('Https server closed successfully');
923
752
  this.listening = false;
@@ -928,13 +757,7 @@ export class Frontend extends EventEmitter {
928
757
  }
929
758
  this.log.debug('Frontend stopped successfully');
930
759
  }
931
- /**
932
- * Retrieves the api settings data.
933
- *
934
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
935
- */
936
760
  async getApiSettings() {
937
- // Update the variable system information properties
938
761
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
939
762
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
940
763
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -944,7 +767,6 @@ export class Frontend extends EventEmitter {
944
767
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
945
768
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
946
769
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
947
- // Create the matterbridge information
948
770
  const info = {
949
771
  homeDirectory: this.matterbridge.homeDirectory,
950
772
  rootDirectory: this.matterbridge.rootDirectory,
@@ -980,15 +802,9 @@ export class Frontend extends EventEmitter {
980
802
  };
981
803
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
982
804
  }
983
- /**
984
- * Retrieves the reachable attribute.
985
- *
986
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
987
- * @returns {boolean} The reachable attribute.
988
- */
989
805
  getReachability(device) {
990
806
  if (this.matterbridge.hasCleanupStarted)
991
- return false; // Skip if cleanup has started
807
+ return false;
992
808
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
993
809
  return false;
994
810
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -999,15 +815,9 @@ export class Frontend extends EventEmitter {
999
815
  return true;
1000
816
  return false;
1001
817
  }
1002
- /**
1003
- * Retrieves the power source attribute.
1004
- *
1005
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1006
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1007
- */
1008
818
  getPowerSource(endpoint) {
1009
819
  if (this.matterbridge.hasCleanupStarted)
1010
- return; // Skip if cleanup has started
820
+ return;
1011
821
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1012
822
  return undefined;
1013
823
  const powerSource = (device) => {
@@ -1022,25 +832,16 @@ export class Frontend extends EventEmitter {
1022
832
  }
1023
833
  return;
1024
834
  };
1025
- // Root endpoint
1026
835
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1027
836
  return powerSource(endpoint);
1028
- // Child endpoints
1029
837
  for (const child of endpoint.getChildEndpoints()) {
1030
838
  if (child.hasClusterServer(PowerSource.Cluster.id))
1031
839
  return powerSource(child);
1032
840
  }
1033
841
  }
1034
- /**
1035
- * Retrieves the cluster text description from a given device.
1036
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1037
- *
1038
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1039
- * @returns {string} The attributes description of the cluster servers in the device.
1040
- */
1041
842
  getClusterTextFromDevice(device) {
1042
843
  if (this.matterbridge.hasCleanupStarted)
1043
- return ''; // Skip if cleanup has started
844
+ return '';
1044
845
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1045
846
  return '';
1046
847
  const getUserLabel = (device) => {
@@ -1050,7 +851,6 @@ export class Frontend extends EventEmitter {
1050
851
  if (composed)
1051
852
  return 'Composed: ' + composed.value;
1052
853
  }
1053
- // istanbul ignore next cause is not reachable
1054
854
  return '';
1055
855
  };
1056
856
  const getFixedLabel = (device) => {
@@ -1060,13 +860,11 @@ export class Frontend extends EventEmitter {
1060
860
  if (composed)
1061
861
  return 'Composed: ' + composed.value;
1062
862
  }
1063
- // istanbul ignore next cause is not reacheable
1064
863
  return '';
1065
864
  };
1066
865
  let attributes = '';
1067
866
  let supportedModes = [];
1068
867
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1069
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1070
868
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1071
869
  return;
1072
870
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1156,17 +954,11 @@ export class Frontend extends EventEmitter {
1156
954
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1157
955
  attributes += `${getUserLabel(device)} `;
1158
956
  });
1159
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1160
957
  return attributes.trimStart().trimEnd();
1161
958
  }
1162
- /**
1163
- * Retrieves the registered plugins sanitized for res.json().
1164
- *
1165
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1166
- */
1167
959
  getPlugins() {
1168
960
  if (this.matterbridge.hasCleanupStarted)
1169
- return []; // Skip if cleanup has started
961
+ return [];
1170
962
  const plugins = [];
1171
963
  for (const plugin of this.matterbridge.plugins.array()) {
1172
964
  plugins.push({
@@ -1194,27 +986,18 @@ export class Frontend extends EventEmitter {
1194
986
  schemaJson: plugin.schemaJson,
1195
987
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1196
988
  hasBlackList: plugin.configJson?.blackList !== undefined,
1197
- // Childbridge mode specific data
1198
989
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1199
990
  });
1200
991
  }
1201
992
  return plugins;
1202
993
  }
1203
- /**
1204
- * Retrieves the devices from Matterbridge.
1205
- *
1206
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1207
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1208
- */
1209
994
  getDevices(pluginName) {
1210
995
  if (this.matterbridge.hasCleanupStarted)
1211
- return []; // Skip if cleanup has started
996
+ return [];
1212
997
  const devices = [];
1213
998
  for (const device of this.matterbridge.devices.array()) {
1214
- // Filter by pluginName if provided
1215
999
  if (pluginName && pluginName !== device.plugin)
1216
1000
  continue;
1217
- // Check if the device has the required properties
1218
1001
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1219
1002
  continue;
1220
1003
  devices.push({
@@ -1234,39 +1017,24 @@ export class Frontend extends EventEmitter {
1234
1017
  }
1235
1018
  return devices;
1236
1019
  }
1237
- /**
1238
- * Retrieves the clusters from a given plugin and endpoint number.
1239
- *
1240
- * Response for /api/clusters
1241
- *
1242
- * @param {string} pluginName - The name of the plugin.
1243
- * @param {number} endpointNumber - The endpoint number.
1244
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1245
- */
1246
1020
  getClusters(pluginName, endpointNumber) {
1247
1021
  if (this.matterbridge.hasCleanupStarted)
1248
- return; // Skip if cleanup has started
1022
+ return;
1249
1023
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1250
1024
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1251
1025
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1252
1026
  return;
1253
1027
  }
1254
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1255
- // Get the device types from the main endpoint
1256
1028
  const deviceTypes = [];
1257
1029
  const clusters = [];
1258
1030
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1259
1031
  deviceTypes.push(d.deviceType);
1260
1032
  });
1261
- // Get the clusters from the main endpoint
1262
1033
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1263
1034
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1264
1035
  return;
1265
1036
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1266
1037
  return;
1267
- // console.log(
1268
- // `${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}`,
1269
- // );
1270
1038
  clusters.push({
1271
1039
  endpoint: endpoint.number.toString(),
1272
1040
  number: endpoint.number,
@@ -1280,19 +1048,12 @@ export class Frontend extends EventEmitter {
1280
1048
  attributeLocalValue: attributeValue,
1281
1049
  });
1282
1050
  });
1283
- // Get the child endpoints
1284
1051
  const childEndpoints = endpoint.getChildEndpoints();
1285
- // if (childEndpoints.length === 0) {
1286
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1287
- // }
1288
1052
  childEndpoints.forEach((childEndpoint) => {
1289
- // istanbul ignore if cause is not reachable: should never happen but ...
1290
1053
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1291
1054
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1292
1055
  return;
1293
1056
  }
1294
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1295
- // Get the device types of the child endpoint
1296
1057
  const deviceTypes = [];
1297
1058
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1298
1059
  deviceTypes.push(d.deviceType);
@@ -1302,9 +1063,6 @@ export class Frontend extends EventEmitter {
1302
1063
  return;
1303
1064
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1304
1065
  return;
1305
- // console.log(
1306
- // `${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}`,
1307
- // );
1308
1066
  clusters.push({
1309
1067
  endpoint: childEndpoint.number.toString(),
1310
1068
  number: childEndpoint.number,
@@ -1324,7 +1082,6 @@ export class Frontend extends EventEmitter {
1324
1082
  async generateDiagnostic() {
1325
1083
  this.log.debug('Generating diagnostic...');
1326
1084
  const serverNodes = [];
1327
- // istanbul ignore else
1328
1085
  if (this.matterbridge.bridgeMode === 'bridge') {
1329
1086
  if (this.matterbridge.serverNode)
1330
1087
  serverNodes.push(this.matterbridge.serverNode);
@@ -1335,7 +1092,6 @@ export class Frontend extends EventEmitter {
1335
1092
  serverNodes.push(plugin.serverNode);
1336
1093
  }
1337
1094
  }
1338
- // istanbul ignore next
1339
1095
  for (const device of this.matterbridge.devices.array()) {
1340
1096
  if (device.serverNode)
1341
1097
  serverNodes.push(device.serverNode);
@@ -1359,15 +1115,8 @@ export class Frontend extends EventEmitter {
1359
1115
  values: [...serverNodes],
1360
1116
  })));
1361
1117
  delete Logger.destinations.diagnostic;
1362
- await wait(500); // Wait for the log to be written
1118
+ await wait(500);
1363
1119
  }
1364
- /**
1365
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1366
- *
1367
- * @param {WebSocket} client - The websocket client that sent the message.
1368
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1369
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1370
- */
1371
1120
  async wsMessageHandler(client, message) {
1372
1121
  let data;
1373
1122
  const sendResponse = (data) => {
@@ -1387,7 +1136,7 @@ export class Frontend extends EventEmitter {
1387
1136
  };
1388
1137
  try {
1389
1138
  data = JSON.parse(message.toString());
1390
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1139
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1391
1140
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1392
1141
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1393
1142
  return;
@@ -1461,7 +1210,6 @@ export class Frontend extends EventEmitter {
1461
1210
  return;
1462
1211
  })
1463
1212
  .catch((_error) => {
1464
- //
1465
1213
  });
1466
1214
  }
1467
1215
  else {
@@ -1509,7 +1257,6 @@ export class Frontend extends EventEmitter {
1509
1257
  return;
1510
1258
  })
1511
1259
  .catch((_error) => {
1512
- //
1513
1260
  });
1514
1261
  }
1515
1262
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1535,7 +1282,6 @@ export class Frontend extends EventEmitter {
1535
1282
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1536
1283
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1537
1284
  if (plugin.serverNode) {
1538
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1539
1285
  await this.matterbridge.stopServerNode(plugin.serverNode);
1540
1286
  plugin.serverNode = undefined;
1541
1287
  }
@@ -1545,20 +1291,18 @@ export class Frontend extends EventEmitter {
1545
1291
  this.matterbridge.devices.remove(device);
1546
1292
  }
1547
1293
  }
1548
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1549
1294
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1550
1295
  await this.matterbridge.createDynamicPlugin(plugin);
1551
1296
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1552
- plugin.restartRequired = false; // Reset plugin restartRequired
1297
+ plugin.restartRequired = false;
1553
1298
  let needRestart = 0;
1554
1299
  for (const plugin of this.matterbridge.plugins) {
1555
1300
  if (plugin.restartRequired)
1556
1301
  needRestart++;
1557
1302
  }
1558
1303
  if (needRestart === 0) {
1559
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1304
+ this.wssSendRestartNotRequired(true);
1560
1305
  }
1561
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1562
1306
  if (plugin.serverNode)
1563
1307
  await this.matterbridge.startServerNode(plugin.serverNode);
1564
1308
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1824,22 +1568,22 @@ export class Frontend extends EventEmitter {
1824
1568
  if (isValidString(data.params.value, 4)) {
1825
1569
  this.log.debug('Matterbridge logger level:', data.params.value);
1826
1570
  if (data.params.value === 'Debug') {
1827
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1571
+ await this.matterbridge.setLogLevel("debug");
1828
1572
  }
1829
1573
  else if (data.params.value === 'Info') {
1830
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1574
+ await this.matterbridge.setLogLevel("info");
1831
1575
  }
1832
1576
  else if (data.params.value === 'Notice') {
1833
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1577
+ await this.matterbridge.setLogLevel("notice");
1834
1578
  }
1835
1579
  else if (data.params.value === 'Warn') {
1836
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1580
+ await this.matterbridge.setLogLevel("warn");
1837
1581
  }
1838
1582
  else if (data.params.value === 'Error') {
1839
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1583
+ await this.matterbridge.setLogLevel("error");
1840
1584
  }
1841
1585
  else if (data.params.value === 'Fatal') {
1842
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1586
+ await this.matterbridge.setLogLevel("fatal");
1843
1587
  }
1844
1588
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1845
1589
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1850,7 +1594,6 @@ export class Frontend extends EventEmitter {
1850
1594
  this.log.debug('Matterbridge file log:', data.params.value);
1851
1595
  this.matterbridge.fileLogger = data.params.value;
1852
1596
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1853
- // Create the file logger for matterbridge
1854
1597
  if (data.params.value)
1855
1598
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1856
1599
  else
@@ -1879,12 +1622,11 @@ export class Frontend extends EventEmitter {
1879
1622
  else if (data.params.value === 'Fatal') {
1880
1623
  Logger.level = MatterLogLevel.FATAL;
1881
1624
  }
1882
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1883
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1884
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1885
- callbackLogLevel = "info" /* LogLevel.INFO */;
1886
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1887
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1625
+ let callbackLogLevel = "notice";
1626
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1627
+ callbackLogLevel = "info";
1628
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1629
+ callbackLogLevel = "debug";
1888
1630
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
1889
1631
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
1890
1632
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -1936,7 +1678,6 @@ export class Frontend extends EventEmitter {
1936
1678
  }
1937
1679
  break;
1938
1680
  case 'setmatterport':
1939
- // eslint-disable-next-line no-case-declarations
1940
1681
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1941
1682
  if (isValidNumber(port, 5540, 5600)) {
1942
1683
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -1956,7 +1697,6 @@ export class Frontend extends EventEmitter {
1956
1697
  }
1957
1698
  break;
1958
1699
  case 'setmatterdiscriminator':
1959
- // eslint-disable-next-line no-case-declarations
1960
1700
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1961
1701
  if (isValidNumber(discriminator, 0, 4095)) {
1962
1702
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -1976,7 +1716,6 @@ export class Frontend extends EventEmitter {
1976
1716
  }
1977
1717
  break;
1978
1718
  case 'setmatterpasscode':
1979
- // eslint-disable-next-line no-case-declarations
1980
1719
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1981
1720
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1982
1721
  this.matterbridge.passcode = passcode;
@@ -2022,19 +1761,15 @@ export class Frontend extends EventEmitter {
2022
1761
  return;
2023
1762
  }
2024
1763
  const config = plugin.configJson;
2025
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2026
1764
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2027
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2028
1765
  if (select === 'serial')
2029
1766
  this.log.info(`Selected device serial ${data.params.serial}`);
2030
1767
  if (select === 'name')
2031
1768
  this.log.info(`Selected device name ${data.params.name}`);
2032
1769
  if (config && select && (select === 'serial' || select === 'name')) {
2033
- // Remove postfix from the serial if it exists
2034
1770
  if (config.postfix) {
2035
1771
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2036
1772
  }
2037
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2038
1773
  if (isValidArray(config.whiteList, 1)) {
2039
1774
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2040
1775
  config.whiteList.push(data.params.serial);
@@ -2043,7 +1778,6 @@ export class Frontend extends EventEmitter {
2043
1778
  config.whiteList.push(data.params.name);
2044
1779
  }
2045
1780
  }
2046
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2047
1781
  if (isValidArray(config.blackList, 1)) {
2048
1782
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2049
1783
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2071,9 +1805,7 @@ export class Frontend extends EventEmitter {
2071
1805
  return;
2072
1806
  }
2073
1807
  const config = plugin.configJson;
2074
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2075
1808
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2076
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2077
1809
  if (select === 'serial')
2078
1810
  this.log.info(`Unselected device serial ${data.params.serial}`);
2079
1811
  if (select === 'name')
@@ -2082,7 +1814,6 @@ export class Frontend extends EventEmitter {
2082
1814
  if (config.postfix) {
2083
1815
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2084
1816
  }
2085
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2086
1817
  if (isValidArray(config.whiteList, 1)) {
2087
1818
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2088
1819
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2091,7 +1822,6 @@ export class Frontend extends EventEmitter {
2091
1822
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2092
1823
  }
2093
1824
  }
2094
- // Add the serial to the blackList
2095
1825
  if (isValidArray(config.blackList)) {
2096
1826
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2097
1827
  config.blackList.push(data.params.serial);
@@ -2114,7 +1844,6 @@ export class Frontend extends EventEmitter {
2114
1844
  }
2115
1845
  }
2116
1846
  else {
2117
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2118
1847
  const localData = data;
2119
1848
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2120
1849
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2124,46 +1853,23 @@ export class Frontend extends EventEmitter {
2124
1853
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2125
1854
  }
2126
1855
  }
2127
- /**
2128
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2129
- *
2130
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2131
- * @param {string} time - The time string of the message
2132
- * @param {string} name - The logger name of the message
2133
- * @param {string} message - The content of the message.
2134
- *
2135
- * @remarks
2136
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2137
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2138
- * The function sends the message to all connected clients.
2139
- */
2140
1856
  wssSendLogMessage(level, time, name, message) {
2141
1857
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2142
1858
  return;
2143
1859
  if (!level || !time || !name || !message)
2144
1860
  return;
2145
- // Remove ANSI escape codes from the message
2146
- // eslint-disable-next-line no-control-regex
2147
1861
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2148
- // Remove leading asterisks from the message
2149
1862
  message = message.replace(/^\*+/, '');
2150
- // Replace all occurrences of \t and \n
2151
1863
  message = message.replace(/[\t\n]/g, '');
2152
- // Remove non-printable characters
2153
- // eslint-disable-next-line no-control-regex
2154
1864
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2155
- // Replace all occurrences of \" with "
2156
1865
  message = message.replace(/\\"/g, '"');
2157
- // Define the maximum allowed length for continuous characters without a space
2158
1866
  const maxContinuousLength = 100;
2159
1867
  const keepStartLength = 20;
2160
1868
  const keepEndLength = 20;
2161
- // Split the message into words
2162
1869
  if (level !== 'spawn') {
2163
1870
  message = message
2164
1871
  .split(' ')
2165
1872
  .map((word) => {
2166
- // If the word length exceeds the max continuous length, insert spaces and truncate
2167
1873
  if (word.length > maxContinuousLength) {
2168
1874
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2169
1875
  }
@@ -2171,34 +1877,14 @@ export class Frontend extends EventEmitter {
2171
1877
  })
2172
1878
  .join(' ');
2173
1879
  }
2174
- // Send the message to all connected clients
2175
1880
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2176
1881
  }
2177
- /**
2178
- * Sends a need to refresh WebSocket message to all connected clients.
2179
- *
2180
- * @param {string} changed - The changed value.
2181
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2182
- * possible values for changed:
2183
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2184
- * - 'plugins'
2185
- * - 'devices'
2186
- * - 'matter' with param 'matter' (QRDiv component)
2187
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2188
- */
2189
1882
  wssSendRefreshRequired(changed, params) {
2190
1883
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2191
1884
  return;
2192
1885
  this.log.debug('Sending a refresh required message to all connected clients');
2193
- // Send the message to all connected clients
2194
1886
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2195
1887
  }
2196
- /**
2197
- * Sends a need to restart WebSocket message to all connected clients.
2198
- *
2199
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2200
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2201
- */
2202
1888
  wssSendRestartRequired(snackbar = true, fixed = false) {
2203
1889
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2204
1890
  return;
@@ -2207,14 +1893,8 @@ export class Frontend extends EventEmitter {
2207
1893
  this.matterbridge.fixedRestartRequired = fixed;
2208
1894
  if (snackbar === true)
2209
1895
  this.wssSendSnackbarMessage(`Restart required`, 0);
2210
- // Send the message to all connected clients
2211
1896
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2212
1897
  }
2213
- /**
2214
- * Sends a no need to restart WebSocket message to all connected clients.
2215
- *
2216
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2217
- */
2218
1898
  wssSendRestartNotRequired(snackbar = true) {
2219
1899
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2220
1900
  return;
@@ -2222,133 +1902,57 @@ export class Frontend extends EventEmitter {
2222
1902
  this.matterbridge.restartRequired = false;
2223
1903
  if (snackbar === true)
2224
1904
  this.wssSendCloseSnackbarMessage(`Restart required`);
2225
- // Send the message to all connected clients
2226
1905
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2227
1906
  }
2228
- /**
2229
- * Sends a need to update WebSocket message to all connected clients.
2230
- *
2231
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2232
- */
2233
1907
  wssSendUpdateRequired(devVersion = false) {
2234
1908
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2235
1909
  return;
2236
1910
  this.log.debug('Sending an update required message to all connected clients');
2237
1911
  this.matterbridge.updateRequired = true;
2238
- // Send the message to all connected clients
2239
1912
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2240
1913
  }
2241
- /**
2242
- * Sends a cpu update message to all connected clients.
2243
- *
2244
- * @param {number} cpuUsage - The CPU usage percentage to send.
2245
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2246
- */
2247
1914
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2248
1915
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2249
1916
  return;
2250
1917
  if (hasParameter('debug'))
2251
1918
  this.log.debug('Sending a cpu update message to all connected clients');
2252
- // Send the message to all connected clients
2253
1919
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 } });
2254
1920
  }
2255
- /**
2256
- * Sends a memory update message to all connected clients.
2257
- *
2258
- * @param {string} totalMemory - The total memory in bytes.
2259
- * @param {string} freeMemory - The free memory in bytes.
2260
- * @param {string} rss - The resident set size in bytes.
2261
- * @param {string} heapTotal - The total heap memory in bytes.
2262
- * @param {string} heapUsed - The used heap memory in bytes.
2263
- * @param {string} external - The external memory in bytes.
2264
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2265
- */
2266
1921
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2267
1922
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2268
1923
  return;
2269
1924
  if (hasParameter('debug'))
2270
1925
  this.log.debug('Sending a memory update message to all connected clients');
2271
- // Send the message to all connected clients
2272
1926
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2273
1927
  }
2274
- /**
2275
- * Sends an uptime update message to all connected clients.
2276
- *
2277
- * @param {string} systemUptime - The system uptime in a human-readable format.
2278
- * @param {string} processUptime - The process uptime in a human-readable format.
2279
- */
2280
1928
  wssSendUptimeUpdate(systemUptime, processUptime) {
2281
1929
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2282
1930
  return;
2283
1931
  if (hasParameter('debug'))
2284
1932
  this.log.debug('Sending a uptime update message to all connected clients');
2285
- // Send the message to all connected clients
2286
1933
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2287
1934
  }
2288
- /**
2289
- * Sends an open snackbar message to all connected clients.
2290
- *
2291
- * @param {string} message - The message to send.
2292
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2293
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2294
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2295
- *
2296
- * @remarks
2297
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2298
- */
2299
1935
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2300
1936
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2301
1937
  return;
2302
1938
  this.log.debug('Sending a snackbar message to all connected clients');
2303
- // Send the message to all connected clients
2304
1939
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2305
1940
  }
2306
- /**
2307
- * Sends a close snackbar message to all connected clients.
2308
- * It will close the snackbar message with the same message and timeout = 0.
2309
- *
2310
- * @param {string} message - The message to send.
2311
- */
2312
1941
  wssSendCloseSnackbarMessage(message) {
2313
1942
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2314
1943
  return;
2315
1944
  this.log.debug('Sending a close snackbar message to all connected clients');
2316
- // Send the message to all connected clients
2317
1945
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2318
1946
  }
2319
- /**
2320
- * Sends an attribute update message to all connected WebSocket clients.
2321
- *
2322
- * @param {string | undefined} plugin - The name of the plugin.
2323
- * @param {string | undefined} serialNumber - The serial number of the device.
2324
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2325
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2326
- * @param {string} id - The endpoint id where the attribute belongs.
2327
- * @param {string} cluster - The cluster name where the attribute belongs.
2328
- * @param {string} attribute - The name of the attribute that changed.
2329
- * @param {number | string | boolean} value - The new value of the attribute.
2330
- *
2331
- * @remarks
2332
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2333
- * with the updated attribute information.
2334
- */
2335
1947
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2336
1948
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2337
1949
  return;
2338
1950
  this.log.debug('Sending an attribute update message to all connected clients');
2339
- // Send the message to all connected clients
2340
1951
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2341
1952
  }
2342
- /**
2343
- * Sends a message to all connected clients.
2344
- * This is an helper function to send a broadcast message to all connected clients.
2345
- *
2346
- * @param {WsMessageBroadcast} msg - The message to send.
2347
- */
2348
1953
  wssBroadcastMessage(msg) {
2349
1954
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2350
1955
  return;
2351
- // Send the message to all connected clients
2352
1956
  const stringifiedMsg = JSON.stringify(msg);
2353
1957
  if (msg.method !== 'log')
2354
1958
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -2359,4 +1963,3 @@ export class Frontend extends EventEmitter {
2359
1963
  });
2360
1964
  }
2361
1965
  }
2362
- //# sourceMappingURL=frontend.js.map