matterbridge 3.5.2 → 3.5.3-dev-20260202-e19e9b6

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 (280) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README-DOCKER.md +2 -2
  3. package/README.md +14 -9
  4. package/dist/broadcastServer.d.ts +0 -115
  5. package/dist/broadcastServer.js +0 -117
  6. package/dist/broadcastServerTypes.d.ts +0 -43
  7. package/dist/broadcastServerTypes.js +0 -24
  8. package/dist/checkUpdates.d.ts +0 -75
  9. package/dist/checkUpdates.js +1 -91
  10. package/dist/cli.d.ts +0 -24
  11. package/dist/cli.js +1 -97
  12. package/dist/cliEmitter.d.ts +0 -36
  13. package/dist/cliEmitter.js +0 -37
  14. package/dist/cliHistory.d.ts +0 -42
  15. package/dist/cliHistory.js +0 -38
  16. package/dist/clusters/export.d.ts +0 -1
  17. package/dist/clusters/export.js +0 -2
  18. package/dist/deviceManager.d.ts +0 -108
  19. package/dist/deviceManager.js +1 -114
  20. package/dist/devices/airConditioner.d.ts +0 -75
  21. package/dist/devices/airConditioner.js +0 -57
  22. package/dist/devices/basicVideoPlayer.d.ts +0 -58
  23. package/dist/devices/basicVideoPlayer.js +1 -56
  24. package/dist/devices/batteryStorage.d.ts +0 -43
  25. package/dist/devices/batteryStorage.js +1 -48
  26. package/dist/devices/castingVideoPlayer.d.ts +0 -63
  27. package/dist/devices/castingVideoPlayer.js +2 -65
  28. package/dist/devices/cooktop.d.ts +0 -55
  29. package/dist/devices/cooktop.js +0 -56
  30. package/dist/devices/dishwasher.d.ts +0 -55
  31. package/dist/devices/dishwasher.js +0 -57
  32. package/dist/devices/evse.d.ts +0 -57
  33. package/dist/devices/evse.js +10 -74
  34. package/dist/devices/export.d.ts +0 -1
  35. package/dist/devices/export.js +0 -5
  36. package/dist/devices/extractorHood.d.ts +0 -41
  37. package/dist/devices/extractorHood.js +0 -43
  38. package/dist/devices/heatPump.d.ts +0 -43
  39. package/dist/devices/heatPump.js +2 -50
  40. package/dist/devices/laundryDryer.d.ts +0 -58
  41. package/dist/devices/laundryDryer.js +3 -62
  42. package/dist/devices/laundryWasher.d.ts +0 -64
  43. package/dist/devices/laundryWasher.js +4 -70
  44. package/dist/devices/microwaveOven.d.ts +1 -77
  45. package/dist/devices/microwaveOven.js +5 -88
  46. package/dist/devices/oven.d.ts +0 -82
  47. package/dist/devices/oven.js +0 -85
  48. package/dist/devices/refrigerator.d.ts +0 -100
  49. package/dist/devices/refrigerator.js +0 -102
  50. package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
  51. package/dist/devices/roboticVacuumCleaner.js +9 -100
  52. package/dist/devices/solarPower.d.ts +0 -36
  53. package/dist/devices/solarPower.js +0 -38
  54. package/dist/devices/speaker.d.ts +0 -79
  55. package/dist/devices/speaker.js +0 -84
  56. package/dist/devices/temperatureControl.d.ts +0 -21
  57. package/dist/devices/temperatureControl.js +3 -24
  58. package/dist/devices/waterHeater.d.ts +0 -74
  59. package/dist/devices/waterHeater.js +2 -82
  60. package/dist/dgram/export.d.ts +0 -1
  61. package/dist/dgram/export.js +0 -1
  62. package/dist/frontend.d.ts +0 -187
  63. package/dist/frontend.js +37 -498
  64. package/dist/frontendTypes.d.ts +0 -57
  65. package/dist/frontendTypes.js +0 -45
  66. package/dist/helpers.d.ts +0 -43
  67. package/dist/helpers.js +0 -54
  68. package/dist/index.d.ts +0 -23
  69. package/dist/index.js +0 -25
  70. package/dist/jestutils/export.d.ts +0 -1
  71. package/dist/jestutils/export.js +0 -1
  72. package/dist/jestutils/jestHelpers.d.ts +0 -255
  73. package/dist/jestutils/jestHelpers.js +15 -371
  74. package/dist/logger/export.d.ts +0 -1
  75. package/dist/logger/export.js +0 -1
  76. package/dist/matter/behaviors.d.ts +0 -1
  77. package/dist/matter/behaviors.js +0 -2
  78. package/dist/matter/clusters.d.ts +0 -1
  79. package/dist/matter/clusters.js +0 -2
  80. package/dist/matter/devices.d.ts +0 -1
  81. package/dist/matter/devices.js +0 -2
  82. package/dist/matter/endpoints.d.ts +0 -1
  83. package/dist/matter/endpoints.js +0 -2
  84. package/dist/matter/export.d.ts +0 -1
  85. package/dist/matter/export.js +0 -2
  86. package/dist/matter/types.d.ts +0 -1
  87. package/dist/matter/types.js +0 -2
  88. package/dist/matterNode.d.ts +0 -258
  89. package/dist/matterNode.js +8 -359
  90. package/dist/matterbridge.d.ts +0 -373
  91. package/dist/matterbridge.js +46 -854
  92. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  93. package/dist/matterbridgeAccessoryPlatform.js +0 -50
  94. package/dist/matterbridgeBehaviors.d.ts +0 -24
  95. package/dist/matterbridgeBehaviors.js +5 -65
  96. package/dist/matterbridgeDeviceTypes.d.ts +0 -649
  97. package/dist/matterbridgeDeviceTypes.js +6 -673
  98. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  99. package/dist/matterbridgeDynamicPlatform.js +0 -50
  100. package/dist/matterbridgeEndpoint.d.ts +0 -1369
  101. package/dist/matterbridgeEndpoint.js +54 -1507
  102. package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
  103. package/dist/matterbridgeEndpointHelpers.js +20 -482
  104. package/dist/matterbridgeEndpointTypes.d.ts +0 -70
  105. package/dist/matterbridgeEndpointTypes.js +0 -25
  106. package/dist/matterbridgePlatform.d.ts +0 -434
  107. package/dist/matterbridgePlatform.js +1 -472
  108. package/dist/matterbridgePlatformTypes.d.ts +0 -29
  109. package/dist/matterbridgePlatformTypes.js +0 -24
  110. package/dist/matterbridgeTypes.d.ts +0 -46
  111. package/dist/matterbridgeTypes.js +0 -26
  112. package/dist/mb_coap.d.ts +0 -23
  113. package/dist/mb_coap.js +3 -41
  114. package/dist/mb_health.d.ts +0 -67
  115. package/dist/mb_health.js +0 -70
  116. package/dist/mb_mdns.d.ts +0 -23
  117. package/dist/mb_mdns.js +36 -94
  118. package/dist/pluginManager.d.ts +0 -305
  119. package/dist/pluginManager.js +5 -342
  120. package/dist/shelly.d.ts +0 -157
  121. package/dist/shelly.js +7 -178
  122. package/dist/spawn.d.ts +0 -32
  123. package/dist/spawn.js +1 -71
  124. package/dist/storage/export.d.ts +0 -1
  125. package/dist/storage/export.js +0 -1
  126. package/dist/utils/export.d.ts +0 -1
  127. package/dist/utils/export.js +0 -1
  128. package/dist/worker.d.ts +0 -61
  129. package/dist/worker.js +4 -65
  130. package/dist/workerCheckUpdates.d.ts +0 -24
  131. package/dist/workerCheckUpdates.js +5 -36
  132. package/dist/workerGlobalPrefix.d.ts +0 -24
  133. package/dist/workerGlobalPrefix.js +5 -36
  134. package/dist/workerTypes.d.ts +0 -25
  135. package/dist/workerTypes.js +0 -24
  136. package/frontend/build/assets/index.js +4 -4
  137. package/frontend/build/assets/vendor_emotion.js +1 -1
  138. package/frontend/build/assets/vendor_lodash.js +1 -1
  139. package/frontend/build/assets/vendor_mdi.js +1 -1
  140. package/frontend/build/assets/vendor_mui.js +22 -22
  141. package/frontend/build/assets/vendor_node_modules.js +20 -20
  142. package/frontend/build/assets/vendor_notistack.js +2 -2
  143. package/frontend/build/assets/vendor_qrcode.js +1 -1
  144. package/frontend/build/assets/vendor_rjsf.js +8 -8
  145. package/frontend/build/index.html +1 -1
  146. package/frontend/package.json +48 -47
  147. package/npm-shrinkwrap.json +77 -47
  148. package/package.json +7 -7
  149. package/dist/broadcastServer.d.ts.map +0 -1
  150. package/dist/broadcastServer.js.map +0 -1
  151. package/dist/broadcastServerTypes.d.ts.map +0 -1
  152. package/dist/broadcastServerTypes.js.map +0 -1
  153. package/dist/checkUpdates.d.ts.map +0 -1
  154. package/dist/checkUpdates.js.map +0 -1
  155. package/dist/cli.d.ts.map +0 -1
  156. package/dist/cli.js.map +0 -1
  157. package/dist/cliEmitter.d.ts.map +0 -1
  158. package/dist/cliEmitter.js.map +0 -1
  159. package/dist/cliHistory.d.ts.map +0 -1
  160. package/dist/cliHistory.js.map +0 -1
  161. package/dist/clusters/export.d.ts.map +0 -1
  162. package/dist/clusters/export.js.map +0 -1
  163. package/dist/deviceManager.d.ts.map +0 -1
  164. package/dist/deviceManager.js.map +0 -1
  165. package/dist/devices/airConditioner.d.ts.map +0 -1
  166. package/dist/devices/airConditioner.js.map +0 -1
  167. package/dist/devices/basicVideoPlayer.d.ts.map +0 -1
  168. package/dist/devices/basicVideoPlayer.js.map +0 -1
  169. package/dist/devices/batteryStorage.d.ts.map +0 -1
  170. package/dist/devices/batteryStorage.js.map +0 -1
  171. package/dist/devices/castingVideoPlayer.d.ts.map +0 -1
  172. package/dist/devices/castingVideoPlayer.js.map +0 -1
  173. package/dist/devices/cooktop.d.ts.map +0 -1
  174. package/dist/devices/cooktop.js.map +0 -1
  175. package/dist/devices/dishwasher.d.ts.map +0 -1
  176. package/dist/devices/dishwasher.js.map +0 -1
  177. package/dist/devices/evse.d.ts.map +0 -1
  178. package/dist/devices/evse.js.map +0 -1
  179. package/dist/devices/export.d.ts.map +0 -1
  180. package/dist/devices/export.js.map +0 -1
  181. package/dist/devices/extractorHood.d.ts.map +0 -1
  182. package/dist/devices/extractorHood.js.map +0 -1
  183. package/dist/devices/heatPump.d.ts.map +0 -1
  184. package/dist/devices/heatPump.js.map +0 -1
  185. package/dist/devices/laundryDryer.d.ts.map +0 -1
  186. package/dist/devices/laundryDryer.js.map +0 -1
  187. package/dist/devices/laundryWasher.d.ts.map +0 -1
  188. package/dist/devices/laundryWasher.js.map +0 -1
  189. package/dist/devices/microwaveOven.d.ts.map +0 -1
  190. package/dist/devices/microwaveOven.js.map +0 -1
  191. package/dist/devices/oven.d.ts.map +0 -1
  192. package/dist/devices/oven.js.map +0 -1
  193. package/dist/devices/refrigerator.d.ts.map +0 -1
  194. package/dist/devices/refrigerator.js.map +0 -1
  195. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  196. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  197. package/dist/devices/solarPower.d.ts.map +0 -1
  198. package/dist/devices/solarPower.js.map +0 -1
  199. package/dist/devices/speaker.d.ts.map +0 -1
  200. package/dist/devices/speaker.js.map +0 -1
  201. package/dist/devices/temperatureControl.d.ts.map +0 -1
  202. package/dist/devices/temperatureControl.js.map +0 -1
  203. package/dist/devices/waterHeater.d.ts.map +0 -1
  204. package/dist/devices/waterHeater.js.map +0 -1
  205. package/dist/dgram/export.d.ts.map +0 -1
  206. package/dist/dgram/export.js.map +0 -1
  207. package/dist/frontend.d.ts.map +0 -1
  208. package/dist/frontend.js.map +0 -1
  209. package/dist/frontendTypes.d.ts.map +0 -1
  210. package/dist/frontendTypes.js.map +0 -1
  211. package/dist/helpers.d.ts.map +0 -1
  212. package/dist/helpers.js.map +0 -1
  213. package/dist/index.d.ts.map +0 -1
  214. package/dist/index.js.map +0 -1
  215. package/dist/jestutils/export.d.ts.map +0 -1
  216. package/dist/jestutils/export.js.map +0 -1
  217. package/dist/jestutils/jestHelpers.d.ts.map +0 -1
  218. package/dist/jestutils/jestHelpers.js.map +0 -1
  219. package/dist/logger/export.d.ts.map +0 -1
  220. package/dist/logger/export.js.map +0 -1
  221. package/dist/matter/behaviors.d.ts.map +0 -1
  222. package/dist/matter/behaviors.js.map +0 -1
  223. package/dist/matter/clusters.d.ts.map +0 -1
  224. package/dist/matter/clusters.js.map +0 -1
  225. package/dist/matter/devices.d.ts.map +0 -1
  226. package/dist/matter/devices.js.map +0 -1
  227. package/dist/matter/endpoints.d.ts.map +0 -1
  228. package/dist/matter/endpoints.js.map +0 -1
  229. package/dist/matter/export.d.ts.map +0 -1
  230. package/dist/matter/export.js.map +0 -1
  231. package/dist/matter/types.d.ts.map +0 -1
  232. package/dist/matter/types.js.map +0 -1
  233. package/dist/matterNode.d.ts.map +0 -1
  234. package/dist/matterNode.js.map +0 -1
  235. package/dist/matterbridge.d.ts.map +0 -1
  236. package/dist/matterbridge.js.map +0 -1
  237. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  238. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  239. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  240. package/dist/matterbridgeBehaviors.js.map +0 -1
  241. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  242. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  243. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  244. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  245. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  246. package/dist/matterbridgeEndpoint.js.map +0 -1
  247. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  248. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  249. package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
  250. package/dist/matterbridgeEndpointTypes.js.map +0 -1
  251. package/dist/matterbridgePlatform.d.ts.map +0 -1
  252. package/dist/matterbridgePlatform.js.map +0 -1
  253. package/dist/matterbridgePlatformTypes.d.ts.map +0 -1
  254. package/dist/matterbridgePlatformTypes.js.map +0 -1
  255. package/dist/matterbridgeTypes.d.ts.map +0 -1
  256. package/dist/matterbridgeTypes.js.map +0 -1
  257. package/dist/mb_coap.d.ts.map +0 -1
  258. package/dist/mb_coap.js.map +0 -1
  259. package/dist/mb_health.d.ts.map +0 -1
  260. package/dist/mb_health.js.map +0 -1
  261. package/dist/mb_mdns.d.ts.map +0 -1
  262. package/dist/mb_mdns.js.map +0 -1
  263. package/dist/pluginManager.d.ts.map +0 -1
  264. package/dist/pluginManager.js.map +0 -1
  265. package/dist/shelly.d.ts.map +0 -1
  266. package/dist/shelly.js.map +0 -1
  267. package/dist/spawn.d.ts.map +0 -1
  268. package/dist/spawn.js.map +0 -1
  269. package/dist/storage/export.d.ts.map +0 -1
  270. package/dist/storage/export.js.map +0 -1
  271. package/dist/utils/export.d.ts.map +0 -1
  272. package/dist/utils/export.js.map +0 -1
  273. package/dist/worker.d.ts.map +0 -1
  274. package/dist/worker.js.map +0 -1
  275. package/dist/workerCheckUpdates.d.ts.map +0 -1
  276. package/dist/workerCheckUpdates.js.map +0 -1
  277. package/dist/workerGlobalPrefix.d.ts.map +0 -1
  278. package/dist/workerGlobalPrefix.js.map +0 -1
  279. package/dist/workerTypes.d.ts.map +0 -1
  280. package/dist/workerTypes.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.3
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 */ /* istanbul ignore next */
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';
@@ -36,7 +10,6 @@ import { FabricIndex } from '@matter/types/datatype';
36
10
  import { CommissioningOptions } from '@matter/types/commissioning';
37
11
  import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
38
12
  import { PowerSource } from '@matter/types/clusters/power-source';
39
- // @matterbridge
40
13
  import { createZip, formatBytes, formatPercent, formatUptime, getParameter, hasParameter, inspectError, isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString, wait, withTimeout } from '@matterbridge/utils';
41
14
  import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
42
15
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
@@ -60,7 +33,7 @@ export class Frontend extends EventEmitter {
60
33
  constructor(matterbridge) {
61
34
  super();
62
35
  this.matterbridge = matterbridge;
63
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
36
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
64
37
  this.log.logNameColor = '\x1b[38;5;97m';
65
38
  this.server = new BroadcastServer('frontend', this.log);
66
39
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -71,7 +44,6 @@ export class Frontend extends EventEmitter {
71
44
  }
72
45
  async msgHandler(msg) {
73
46
  if (this.server.isWorkerRequest(msg)) {
74
- // istanbul ignore else
75
47
  if (this.verbose)
76
48
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
77
49
  switch (msg.type) {
@@ -123,13 +95,11 @@ export class Frontend extends EventEmitter {
123
95
  this.server.respond({ ...msg, result: { success: true } });
124
96
  break;
125
97
  default:
126
- // istanbul ignore next
127
98
  if (this.verbose)
128
99
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
129
100
  }
130
101
  }
131
102
  if (this.server.isWorkerResponse(msg) && msg.result) {
132
- // istanbul ignore next
133
103
  if (this.verbose)
134
104
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
135
105
  switch (msg.type) {
@@ -173,55 +143,23 @@ export class Frontend extends EventEmitter {
173
143
  this.port = port;
174
144
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
175
145
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
176
- // Initialize multer with the upload directory
177
146
  const multer = await import('multer');
178
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
147
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
179
148
  const upload = multer.default({ dest: uploadDir });
180
- // Create the express app that serves the frontend
181
149
  const express = await import('express');
182
150
  this.expressApp = express.default();
183
- // Inject logging/debug wrapper for route/middleware registration
184
- /*
185
- const methods = ['get', 'post', 'put', 'delete', 'use'];
186
- for (const method of methods) {
187
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
- const original = (this.expressApp as any)[method].bind(this.expressApp);
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
191
- try {
192
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
193
- return original(path, ...rest);
194
- } catch (err) {
195
- console.error(`[ERROR] Failed to register route: ${path}`);
196
- throw err;
197
- }
198
- };
199
- }
200
- */
201
- // Log all requests to the server for debugging
202
- /*
203
- this.expressApp.use((req, res, next) => {
204
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
205
- next();
206
- });
207
- */
208
- // Serve static files from 'frontend/build' directory
209
151
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
210
- // Create a WebSocket server and attach it to the http or https server
211
152
  this.log.debug(`Creating WebSocketServer...`);
212
153
  const ws = await import('ws');
213
154
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
214
155
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
215
156
  this.webSocketServer.on('connection', (ws, request) => {
216
157
  const clientIp = request.socket.remoteAddress;
217
- // Set the global logger callback for the WebSocketServer
218
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
219
- // istanbul ignore else
220
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
221
- callbackLogLevel = "info" /* LogLevel.INFO */;
222
- // istanbul ignore else
223
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
224
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
158
+ let callbackLogLevel = "notice";
159
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
160
+ callbackLogLevel = "info";
161
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
162
+ callbackLogLevel = "debug";
225
163
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
226
164
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
227
165
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -237,34 +175,23 @@ export class Frontend extends EventEmitter {
237
175
  });
238
176
  ws.on('close', () => {
239
177
  this.log.info('WebSocket client disconnected');
240
- // istanbul ignore else
241
178
  if (this.webSocketServer?.clients.size === 0) {
242
179
  AnsiLogger.setGlobalCallback(undefined);
243
180
  this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
244
181
  this.authClients = [];
245
182
  }
246
183
  });
247
- // istanbul ignore next
248
184
  ws.on('error', (error) => {
249
- // istanbul ignore next
250
185
  this.log.error(`WebSocket client error: ${error}`);
251
186
  });
252
187
  });
253
188
  this.webSocketServer.on('close', () => {
254
189
  this.log.debug(`WebSocketServer closed`);
255
190
  });
256
- /* With { noServer: true } it never fires
257
- this.webSocketServer.on('listening', () => {
258
- this.log.info(`The WebSocketServer is listening`);
259
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
260
- });
261
- */
262
- // istanbul ignore next
263
191
  this.webSocketServer.on('error', (ws, error) => {
264
192
  this.log.error(`WebSocketServer error: ${error}`);
265
193
  });
266
194
  if (!hasParameter('ssl')) {
267
- // Create an HTTP server and attach the express app
268
195
  const http = await import('node:http');
269
196
  try {
270
197
  this.log.debug(`Creating HTTP server...`);
@@ -275,9 +202,7 @@ export class Frontend extends EventEmitter {
275
202
  this.emit('server_error', error);
276
203
  return;
277
204
  }
278
- // Listen on the specified port
279
205
  if (hasParameter('ingress')) {
280
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
281
206
  this.httpServer.listen(this.port, '0.0.0.0', () => {
282
207
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
283
208
  this.listening = true;
@@ -285,17 +210,13 @@ export class Frontend extends EventEmitter {
285
210
  });
286
211
  }
287
212
  else {
288
- // We listen to all available addresses
289
213
  this.httpServer.listen(this.port, getParameter('bind'), () => {
290
214
  const addr = this.httpServer?.address();
291
- // istanbul ignore else
292
215
  if (addr && typeof addr !== 'string') {
293
216
  this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
294
217
  }
295
- // istanbul ignore else
296
218
  if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
297
219
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
298
- // istanbul ignore else
299
220
  if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
300
221
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
301
222
  this.listening = true;
@@ -304,25 +225,19 @@ export class Frontend extends EventEmitter {
304
225
  }
305
226
  this.httpServer.on('upgrade', async (req, socket, head) => {
306
227
  try {
307
- // Only proceed for real WebSocket upgrades
308
- // istanbul ignore next cause is only a safety check
309
228
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
310
229
  this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
311
230
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
312
231
  return socket.destroy();
313
232
  }
314
- // Build a URL so we can read ?password=...
315
233
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
316
- // Validate WebSocket password
317
234
  const password = url.searchParams.get('password') ?? '';
318
235
  if (password !== this.storedPassword) {
319
236
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
320
237
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
321
238
  return socket.destroy();
322
239
  }
323
- // Complete the WebSocket handshake
324
240
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
325
- // istanbul ignore else
326
241
  if (req.socket.remoteAddress)
327
242
  this.authClients.push(req.socket.remoteAddress);
328
243
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
@@ -330,7 +245,6 @@ export class Frontend extends EventEmitter {
330
245
  });
331
246
  }
332
247
  catch (err) {
333
- /* istanbul ignore next: only triggered on unexpected internal error */
334
248
  {
335
249
  inspectError(this.log, 'WebSocket upgrade error:', err);
336
250
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -353,7 +267,6 @@ export class Frontend extends EventEmitter {
353
267
  });
354
268
  }
355
269
  else {
356
- // SSL is enabled, load the certificate and the private key
357
270
  let cert;
358
271
  let key;
359
272
  let ca;
@@ -363,7 +276,6 @@ export class Frontend extends EventEmitter {
363
276
  let httpsServerOptions = {};
364
277
  const fs = await import('node:fs');
365
278
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
366
- // Load the p12 certificate and the passphrase
367
279
  try {
368
280
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
369
281
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -375,7 +287,7 @@ export class Frontend extends EventEmitter {
375
287
  }
376
288
  try {
377
289
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
378
- passphrase = passphrase.trim(); // Ensure no extra characters
290
+ passphrase = passphrase.trim();
379
291
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
380
292
  }
381
293
  catch (error) {
@@ -386,7 +298,6 @@ export class Frontend extends EventEmitter {
386
298
  httpsServerOptions = { pfx, passphrase };
387
299
  }
388
300
  else {
389
- // 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.
390
301
  try {
391
302
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
392
303
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -416,10 +327,9 @@ export class Frontend extends EventEmitter {
416
327
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
417
328
  }
418
329
  if (hasParameter('mtls')) {
419
- httpsServerOptions.requestCert = true; // Request client certificate
420
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
330
+ httpsServerOptions.requestCert = true;
331
+ httpsServerOptions.rejectUnauthorized = true;
421
332
  }
422
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
423
333
  const https = await import('node:https');
424
334
  try {
425
335
  this.log.debug(`Creating HTTPS server...`);
@@ -430,9 +340,7 @@ export class Frontend extends EventEmitter {
430
340
  this.emit('server_error', error);
431
341
  return;
432
342
  }
433
- // Listen on the specified port
434
343
  if (hasParameter('ingress')) {
435
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
436
344
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
437
345
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
438
346
  this.listening = true;
@@ -440,17 +348,13 @@ export class Frontend extends EventEmitter {
440
348
  });
441
349
  }
442
350
  else {
443
- // We listen to all available addresses
444
351
  this.httpsServer.listen(this.port, getParameter('bind'), () => {
445
352
  const addr = this.httpsServer?.address();
446
- // istanbul ignore else
447
353
  if (addr && typeof addr !== 'string') {
448
354
  this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
449
355
  }
450
- // istanbul ignore else
451
356
  if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
452
357
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
453
- // istanbul ignore else
454
358
  if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
455
359
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
456
360
  this.listening = true;
@@ -459,24 +363,18 @@ export class Frontend extends EventEmitter {
459
363
  }
460
364
  this.httpsServer.on('upgrade', async (req, socket, head) => {
461
365
  try {
462
- // Only proceed for real WebSocket upgrades
463
- // istanbul ignore next cause is only a safety check
464
366
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
465
367
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
466
368
  return socket.destroy();
467
369
  }
468
- // Build a URL so we can read ?password=...
469
370
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
470
- // Validate WebSocket password
471
371
  const password = url.searchParams.get('password') ?? '';
472
372
  if (password !== this.storedPassword) {
473
373
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
474
374
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
475
375
  return socket.destroy();
476
376
  }
477
- // Complete the WebSocket handshake
478
377
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
479
- // istanbul ignore else
480
378
  if (req.socket.remoteAddress)
481
379
  this.authClients.push(req.socket.remoteAddress);
482
380
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
@@ -484,7 +382,6 @@ export class Frontend extends EventEmitter {
484
382
  });
485
383
  }
486
384
  catch (err) {
487
- /* istanbul ignore next: only triggered on unexpected internal error */
488
385
  {
489
386
  inspectError(this.log, 'WebSocket upgrade error:', err);
490
387
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -506,7 +403,6 @@ export class Frontend extends EventEmitter {
506
403
  return;
507
404
  });
508
405
  }
509
- // Subscribe to cli events
510
406
  cliEmitter.removeAllListeners();
511
407
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
512
408
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -517,8 +413,6 @@ export class Frontend extends EventEmitter {
517
413
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
518
414
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
519
415
  });
520
- // Endpoint to validate login code
521
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
522
416
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
523
417
  const { password } = req.body;
524
418
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -533,20 +427,17 @@ export class Frontend extends EventEmitter {
533
427
  res.json({ valid: false });
534
428
  }
535
429
  });
536
- // Endpoint to provide health check for docker
537
430
  this.expressApp.get('/health', (req, res) => {
538
431
  this.log.debug('Express received /health');
539
432
  const healthStatus = {
540
- status: 'ok', // Indicate service is healthy
541
- uptime: process.uptime(), // Server uptime in seconds
542
- timestamp: new Date().toISOString(), // Current timestamp
433
+ status: 'ok',
434
+ uptime: process.uptime(),
435
+ timestamp: new Date().toISOString(),
543
436
  };
544
437
  res.status(200).json(healthStatus);
545
438
  });
546
- // Endpoint to provide memory usage details
547
439
  this.expressApp.get('/memory', async (req, res) => {
548
440
  this.log.debug('Express received /memory');
549
- // Memory usage from process
550
441
  const memoryUsageRaw = process.memoryUsage();
551
442
  const memoryUsage = {
552
443
  rss: formatBytes(memoryUsageRaw.rss),
@@ -555,13 +446,10 @@ export class Frontend extends EventEmitter {
555
446
  external: formatBytes(memoryUsageRaw.external),
556
447
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
557
448
  };
558
- // V8 heap statistics
559
449
  const { default: v8 } = await import('node:v8');
560
450
  const heapStatsRaw = v8.getHeapStatistics();
561
451
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
562
- // Format heapStats
563
452
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
564
- // Format heapSpaces
565
453
  const heapSpaces = heapSpacesRaw.map((space) => ({
566
454
  ...space,
567
455
  space_size: formatBytes(space.space_size),
@@ -580,28 +468,24 @@ export class Frontend extends EventEmitter {
580
468
  };
581
469
  res.status(200).json(memoryReport);
582
470
  });
583
- // Endpoint to provide settings
584
471
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
585
472
  this.log.debug('The frontend sent /api/settings');
586
473
  if (!this.validateReq(req, res))
587
474
  return;
588
475
  res.json(await this.getApiSettings());
589
476
  });
590
- // Endpoint to provide plugins
591
477
  this.expressApp.get('/api/plugins', async (req, res) => {
592
478
  this.log.debug('The frontend sent /api/plugins');
593
479
  if (!this.validateReq(req, res))
594
480
  return;
595
481
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
596
482
  });
597
- // Endpoint to provide devices
598
483
  this.expressApp.get('/api/devices', async (req, res) => {
599
484
  this.log.debug('The frontend sent /api/devices');
600
485
  if (!this.validateReq(req, res))
601
486
  return;
602
487
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
603
488
  });
604
- // Endpoint to view the matterbridge log
605
489
  this.expressApp.get('/api/view-mblog', async (req, res) => {
606
490
  this.log.debug('The frontend sent /api/view-mblog');
607
491
  if (!this.validateReq(req, res))
@@ -617,7 +501,6 @@ export class Frontend extends EventEmitter {
617
501
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
618
502
  }
619
503
  });
620
- // Endpoint to view the matter.js log
621
504
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
622
505
  this.log.debug('The frontend sent /api/view-mjlog');
623
506
  if (!this.validateReq(req, res))
@@ -633,7 +516,6 @@ export class Frontend extends EventEmitter {
633
516
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
634
517
  }
635
518
  });
636
- // Endpoint to view the diagnostic.log
637
519
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
638
520
  this.log.debug('The frontend sent /api/view-diagnostic');
639
521
  if (!this.validateReq(req, res))
@@ -646,13 +528,10 @@ export class Frontend extends EventEmitter {
646
528
  res.send(data.slice(29));
647
529
  }
648
530
  catch (error) {
649
- // istanbul ignore next
650
531
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
651
- // istanbul ignore next
652
532
  res.status(500).send('Error reading diagnostic log file.');
653
533
  }
654
534
  });
655
- // Endpoint to download the diagnostic.log
656
535
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
657
536
  this.log.debug(`The frontend sent /api/download-diagnostic`);
658
537
  if (!this.validateReq(req, res))
@@ -665,19 +544,16 @@ export class Frontend extends EventEmitter {
665
544
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
666
545
  }
667
546
  catch (error) {
668
- // istanbul ignore next
669
547
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
670
548
  }
671
549
  res.type('text/plain');
672
550
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
673
- /* istanbul ignore if */
674
551
  if (error) {
675
552
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
676
553
  res.status(500).send('Error downloading the diagnostic log file');
677
554
  }
678
555
  });
679
556
  });
680
- // Endpoint to view the history.html
681
557
  this.expressApp.get('/api/viewhistory', async (req, res) => {
682
558
  this.log.debug('The frontend sent /api/viewhistory');
683
559
  if (!this.validateReq(req, res))
@@ -693,7 +569,6 @@ export class Frontend extends EventEmitter {
693
569
  res.status(500).send('Error reading history file.');
694
570
  }
695
571
  });
696
- // Endpoint to download the history.html
697
572
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
698
573
  this.log.debug(`The frontend sent /api/downloadhistory`);
699
574
  if (!this.validateReq(req, res))
@@ -705,7 +580,6 @@ export class Frontend extends EventEmitter {
705
580
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
706
581
  res.type('text/plain');
707
582
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
708
- /* istanbul ignore if */
709
583
  if (error) {
710
584
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
711
585
  res.status(500).send('Error downloading history file');
@@ -717,7 +591,6 @@ export class Frontend extends EventEmitter {
717
591
  res.status(500).send('Error reading history file.');
718
592
  }
719
593
  });
720
- // Endpoint to view the shelly log
721
594
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
722
595
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
723
596
  if (!this.validateReq(req, res))
@@ -733,7 +606,6 @@ export class Frontend extends EventEmitter {
733
606
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
734
607
  }
735
608
  });
736
- // Endpoint to download the matterbridge log
737
609
  this.expressApp.get('/api/download-mblog', async (req, res) => {
738
610
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
739
611
  if (!this.validateReq(req, res))
@@ -750,14 +622,12 @@ export class Frontend extends EventEmitter {
750
622
  }
751
623
  res.type('text/plain');
752
624
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
753
- /* istanbul ignore if */
754
625
  if (error) {
755
626
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
756
627
  res.status(500).send('Error downloading the matterbridge log file');
757
628
  }
758
629
  });
759
630
  });
760
- // Endpoint to download the matter log
761
631
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
762
632
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
763
633
  if (!this.validateReq(req, res))
@@ -774,14 +644,12 @@ export class Frontend extends EventEmitter {
774
644
  }
775
645
  res.type('text/plain');
776
646
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
777
- /* istanbul ignore if */
778
647
  if (error) {
779
648
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
780
649
  res.status(500).send('Error downloading the matter log file');
781
650
  }
782
651
  });
783
652
  });
784
- // Endpoint to download the shelly log
785
653
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
786
654
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
787
655
  if (!this.validateReq(req, res))
@@ -798,103 +666,87 @@ export class Frontend extends EventEmitter {
798
666
  }
799
667
  res.type('text/plain');
800
668
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
801
- /* istanbul ignore if */
802
669
  if (error) {
803
670
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
804
671
  res.status(500).send('Error downloading Shelly system log file');
805
672
  }
806
673
  });
807
674
  });
808
- // Endpoint to download the matterbridge storage directory
809
675
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
810
676
  this.log.debug('The frontend sent /api/download-mbstorage');
811
677
  if (!this.validateReq(req, res))
812
678
  return;
813
679
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
814
680
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
815
- /* istanbul ignore if */
816
681
  if (error) {
817
682
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
818
683
  res.status(500).send('Error downloading the matterbridge storage file');
819
684
  }
820
685
  });
821
686
  });
822
- // Endpoint to download the matter storage file
823
687
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
824
688
  this.log.debug('The frontend sent /api/download-mjstorage');
825
689
  if (!this.validateReq(req, res))
826
690
  return;
827
691
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
828
692
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
829
- /* istanbul ignore if */
830
693
  if (error) {
831
694
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
832
695
  res.status(500).send('Error downloading the matter storage zip file');
833
696
  }
834
697
  });
835
698
  });
836
- // Endpoint to download the matterbridge plugin directory
837
699
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
838
700
  this.log.debug('The frontend sent /api/download-pluginstorage');
839
701
  if (!this.validateReq(req, res))
840
702
  return;
841
703
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
842
704
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
843
- /* istanbul ignore if */
844
705
  if (error) {
845
706
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
846
707
  res.status(500).send('Error downloading the matterbridge plugin storage file');
847
708
  }
848
709
  });
849
710
  });
850
- // Endpoint to download the matterbridge plugin config files
851
711
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
852
712
  this.log.debug('The frontend sent /api/download-pluginconfig');
853
713
  if (!this.validateReq(req, res))
854
714
  return;
855
715
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
856
716
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
857
- /* istanbul ignore if */
858
717
  if (error) {
859
718
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
860
719
  res.status(500).send('Error downloading the matterbridge plugin config file');
861
720
  }
862
721
  });
863
722
  });
864
- // Endpoint to download the matterbridge backup (created with the backup command)
865
723
  this.expressApp.get('/api/download-backup', async (req, res) => {
866
724
  this.log.debug('The frontend sent /api/download-backup');
867
725
  if (!this.validateReq(req, res))
868
726
  return;
869
727
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
870
- /* istanbul ignore if */
871
728
  if (error) {
872
729
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
873
730
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
874
731
  }
875
732
  });
876
733
  });
877
- // Endpoint to upload a package
878
734
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
879
735
  if (!this.validateReq(req, res))
880
736
  return;
881
737
  const { filename } = req.body;
882
738
  const file = req.file;
883
- /* istanbul ignore if */
884
739
  if (!file || !filename) {
885
740
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
886
741
  res.status(400).send('Invalid request: file and filename are required');
887
742
  return;
888
743
  }
889
744
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
890
- // Define the path where the plugin file will be saved
891
745
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
892
746
  try {
893
- // Move the uploaded file to the specified path
894
747
  const fs = await import('node:fs');
895
748
  await fs.promises.rename(file.path, filePath);
896
749
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
897
- // Install the plugin package
898
750
  if (filename.endsWith('.tgz')) {
899
751
  const { spawnCommand } = await import('./spawn.js');
900
752
  if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
@@ -922,7 +774,6 @@ export class Frontend extends EventEmitter {
922
774
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
923
775
  }
924
776
  });
925
- // Fallback for routing (must be the last route)
926
777
  this.expressApp.use((req, res) => {
927
778
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
928
779
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -933,16 +784,13 @@ export class Frontend extends EventEmitter {
933
784
  async stop() {
934
785
  this.log.debug('Stopping the frontend...');
935
786
  const ws = await import('ws');
936
- // Remove listeners from the express app
937
787
  if (this.expressApp) {
938
788
  this.expressApp.removeAllListeners();
939
789
  this.expressApp = undefined;
940
790
  this.log.debug('Frontend app closed successfully');
941
791
  }
942
- // Close the WebSocket server
943
792
  if (this.webSocketServer) {
944
793
  this.log.debug('Closing WebSocket server...');
945
- // Close all active connections
946
794
  this.webSocketServer.clients.forEach((client) => {
947
795
  if (client.readyState === ws.WebSocket.OPEN) {
948
796
  client.close();
@@ -950,9 +798,7 @@ export class Frontend extends EventEmitter {
950
798
  });
951
799
  await withTimeout(new Promise((resolve) => {
952
800
  this.webSocketServer?.close((error) => {
953
- // istanbul ignore if
954
801
  if (error) {
955
- // istanbul ignore next
956
802
  this.log.error(`Error closing WebSocket server: ${error}`);
957
803
  }
958
804
  else {
@@ -965,27 +811,8 @@ export class Frontend extends EventEmitter {
965
811
  this.webSocketServer.removeAllListeners();
966
812
  this.webSocketServer = undefined;
967
813
  }
968
- // Close the http server
969
814
  if (this.httpServer) {
970
815
  this.log.debug('Closing http server...');
971
- /*
972
- await withTimeout(
973
- new Promise<void>((resolve) => {
974
- this.httpServer?.close((error) => {
975
- if (error) {
976
- // istanbul ignore next
977
- this.log.error(`Error closing http server: ${error}`);
978
- } else {
979
- this.log.debug('Http server closed successfully');
980
- this.emit('server_stopped');
981
- }
982
- resolve();
983
- });
984
- }),
985
- 5000,
986
- false,
987
- );
988
- */
989
816
  this.httpServer.close();
990
817
  this.log.debug('Http server closed successfully');
991
818
  this.listening = false;
@@ -994,27 +821,8 @@ export class Frontend extends EventEmitter {
994
821
  this.httpServer = undefined;
995
822
  this.log.debug('Frontend http server closed successfully');
996
823
  }
997
- // Close the https server
998
824
  if (this.httpsServer) {
999
825
  this.log.debug('Closing https server...');
1000
- /*
1001
- await withTimeout(
1002
- new Promise<void>((resolve) => {
1003
- this.httpsServer?.close((error) => {
1004
- if (error) {
1005
- // istanbul ignore next
1006
- this.log.error(`Error closing https server: ${error}`);
1007
- } else {
1008
- this.log.debug('Https server closed successfully');
1009
- this.emit('server_stopped');
1010
- }
1011
- resolve();
1012
- });
1013
- }),
1014
- 5000,
1015
- false,
1016
- );
1017
- */
1018
826
  this.httpsServer.close();
1019
827
  this.log.debug('Https server closed successfully');
1020
828
  this.listening = false;
@@ -1025,13 +833,7 @@ export class Frontend extends EventEmitter {
1025
833
  }
1026
834
  this.log.debug('Frontend stopped successfully');
1027
835
  }
1028
- /**
1029
- * Retrieves the api settings data.
1030
- *
1031
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
1032
- */
1033
836
  async getApiSettings() {
1034
- // Update the variable system information properties
1035
837
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
1036
838
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
1037
839
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -1041,7 +843,6 @@ export class Frontend extends EventEmitter {
1041
843
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
1042
844
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
1043
845
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
1044
- // Create the matterbridge information
1045
846
  const info = {
1046
847
  homeDirectory: this.matterbridge.homeDirectory,
1047
848
  rootDirectory: this.matterbridge.rootDirectory,
@@ -1077,15 +878,9 @@ export class Frontend extends EventEmitter {
1077
878
  };
1078
879
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
1079
880
  }
1080
- /**
1081
- * Retrieves the reachable attribute.
1082
- *
1083
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
1084
- * @returns {boolean} The reachable attribute.
1085
- */
1086
881
  getReachability(device) {
1087
882
  if (this.matterbridge.hasCleanupStarted)
1088
- return false; // Skip if cleanup has started
883
+ return false;
1089
884
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1090
885
  return false;
1091
886
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -1096,15 +891,9 @@ export class Frontend extends EventEmitter {
1096
891
  return true;
1097
892
  return false;
1098
893
  }
1099
- /**
1100
- * Retrieves the power source attribute.
1101
- *
1102
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1103
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1104
- */
1105
894
  getPowerSource(endpoint) {
1106
895
  if (this.matterbridge.hasCleanupStarted)
1107
- return undefined; // Skip if cleanup has started
896
+ return undefined;
1108
897
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1109
898
  return undefined;
1110
899
  const powerSource = (device) => {
@@ -1119,25 +908,16 @@ export class Frontend extends EventEmitter {
1119
908
  }
1120
909
  return;
1121
910
  };
1122
- // Root endpoint
1123
911
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1124
912
  return powerSource(endpoint);
1125
- // Child endpoints
1126
913
  for (const child of endpoint.getChildEndpoints()) {
1127
- // istanbul ignore else
1128
914
  if (child.hasClusterServer(PowerSource.Cluster.id))
1129
915
  return powerSource(child);
1130
916
  }
1131
917
  }
1132
- /**
1133
- * Retrieves the battery level attribute.
1134
- *
1135
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1136
- * @returns {number | undefined} The battery level attribute.
1137
- */
1138
918
  getBatteryLevel(endpoint) {
1139
919
  if (this.matterbridge.hasCleanupStarted)
1140
- return undefined; // Skip if cleanup has started
920
+ return undefined;
1141
921
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1142
922
  return undefined;
1143
923
  const batteryLevel = (device) => {
@@ -1148,27 +928,16 @@ export class Frontend extends EventEmitter {
1148
928
  }
1149
929
  return undefined;
1150
930
  };
1151
- // Root endpoint
1152
931
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1153
932
  return batteryLevel(endpoint);
1154
- // Child endpoints
1155
933
  for (const child of endpoint.getChildEndpoints()) {
1156
- // istanbul ignore else
1157
934
  if (child.hasClusterServer(PowerSource.Cluster.id))
1158
935
  return batteryLevel(child);
1159
936
  }
1160
937
  }
1161
- /**
1162
- * Retrieves the cluster text description from a given device.
1163
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1164
- *
1165
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1166
- * @returns {string} The attributes description of the cluster servers in the device.
1167
- */
1168
938
  getClusterTextFromDevice(device) {
1169
939
  if (this.matterbridge.hasCleanupStarted)
1170
- return ''; // Skip if cleanup has started
1171
- // istanbul ignore else
940
+ return '';
1172
941
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1173
942
  return '';
1174
943
  const getUserLabel = (device) => {
@@ -1178,7 +947,6 @@ export class Frontend extends EventEmitter {
1178
947
  if (composed)
1179
948
  return 'Composed: ' + composed.value;
1180
949
  }
1181
- // istanbul ignore next cause is not reachable
1182
950
  return '';
1183
951
  };
1184
952
  const getFixedLabel = (device) => {
@@ -1188,13 +956,11 @@ export class Frontend extends EventEmitter {
1188
956
  if (composed)
1189
957
  return 'Composed: ' + composed.value;
1190
958
  }
1191
- // istanbul ignore next cause is not reacheable
1192
959
  return '';
1193
960
  };
1194
961
  let attributes = '';
1195
962
  let supportedModes = [];
1196
963
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1197
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1198
964
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1199
965
  return;
1200
966
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1284,17 +1050,11 @@ export class Frontend extends EventEmitter {
1284
1050
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1285
1051
  attributes += `${getUserLabel(device)} `;
1286
1052
  });
1287
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1288
1053
  return attributes.trimStart().trimEnd();
1289
1054
  }
1290
- /**
1291
- * Retrieves the registered plugins sanitized for res.json().
1292
- *
1293
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1294
- */
1295
1055
  getPlugins() {
1296
1056
  if (this.matterbridge.hasCleanupStarted)
1297
- return []; // Skip if cleanup has started
1057
+ return [];
1298
1058
  const plugins = [];
1299
1059
  for (const plugin of this.matterbridge.plugins.array()) {
1300
1060
  plugins.push({
@@ -1322,27 +1082,18 @@ export class Frontend extends EventEmitter {
1322
1082
  schemaJson: plugin.schemaJson,
1323
1083
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1324
1084
  hasBlackList: plugin.configJson?.blackList !== undefined,
1325
- // Childbridge mode specific data
1326
1085
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1327
1086
  });
1328
1087
  }
1329
1088
  return plugins;
1330
1089
  }
1331
- /**
1332
- * Retrieves the devices from Matterbridge.
1333
- *
1334
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1335
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1336
- */
1337
1090
  getDevices(pluginName) {
1338
1091
  if (this.matterbridge.hasCleanupStarted)
1339
- return []; // Skip if cleanup has started
1092
+ return [];
1340
1093
  const devices = [];
1341
1094
  for (const device of this.matterbridge.devices.array()) {
1342
- // Filter by pluginName if provided
1343
1095
  if (pluginName && pluginName !== device.plugin)
1344
1096
  continue;
1345
- // Check if the device has the required properties
1346
1097
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1347
1098
  continue;
1348
1099
  devices.push({
@@ -1363,39 +1114,24 @@ export class Frontend extends EventEmitter {
1363
1114
  }
1364
1115
  return devices;
1365
1116
  }
1366
- /**
1367
- * Retrieves the clusters from a given plugin and endpoint number.
1368
- *
1369
- * Response for /api/clusters
1370
- *
1371
- * @param {string} pluginName - The name of the plugin.
1372
- * @param {number} endpointNumber - The endpoint number.
1373
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1374
- */
1375
1117
  getClusters(pluginName, endpointNumber) {
1376
1118
  if (this.matterbridge.hasCleanupStarted)
1377
- return; // Skip if cleanup has started
1119
+ return;
1378
1120
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1379
1121
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1380
1122
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1381
1123
  return;
1382
1124
  }
1383
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1384
- // Get the device types from the main endpoint
1385
1125
  const deviceTypes = [];
1386
1126
  const clusters = [];
1387
1127
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1388
1128
  deviceTypes.push(d.deviceType);
1389
1129
  });
1390
- // Get the clusters from the main endpoint
1391
1130
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1392
1131
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1393
1132
  return;
1394
1133
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1395
1134
  return;
1396
- // console.log(
1397
- // `${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}`,
1398
- // );
1399
1135
  clusters.push({
1400
1136
  endpoint: endpoint.number.toString(),
1401
1137
  number: endpoint.number,
@@ -1409,19 +1145,12 @@ export class Frontend extends EventEmitter {
1409
1145
  attributeLocalValue: attributeValue,
1410
1146
  });
1411
1147
  });
1412
- // Get the child endpoints
1413
1148
  const childEndpoints = endpoint.getChildEndpoints();
1414
- // if (childEndpoints.length === 0) {
1415
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1416
- // }
1417
1149
  childEndpoints.forEach((childEndpoint) => {
1418
- // istanbul ignore if cause is not reachable: should never happen but ...
1419
1150
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1420
1151
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1421
1152
  return;
1422
1153
  }
1423
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1424
- // Get the device types of the child endpoint
1425
1154
  const deviceTypes = [];
1426
1155
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1427
1156
  deviceTypes.push(d.deviceType);
@@ -1431,9 +1160,6 @@ export class Frontend extends EventEmitter {
1431
1160
  return;
1432
1161
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1433
1162
  return;
1434
- // console.log(
1435
- // `${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}`,
1436
- // );
1437
1163
  clusters.push({
1438
1164
  endpoint: childEndpoint.number.toString(),
1439
1165
  number: childEndpoint.number,
@@ -1453,7 +1179,6 @@ export class Frontend extends EventEmitter {
1453
1179
  async generateDiagnostic() {
1454
1180
  this.log.debug('Generating diagnostic...');
1455
1181
  const serverNodes = [];
1456
- // istanbul ignore else
1457
1182
  if (this.matterbridge.bridgeMode === 'bridge') {
1458
1183
  if (this.matterbridge.serverNode)
1459
1184
  serverNodes.push(this.matterbridge.serverNode);
@@ -1464,7 +1189,6 @@ export class Frontend extends EventEmitter {
1464
1189
  serverNodes.push(plugin.serverNode);
1465
1190
  }
1466
1191
  }
1467
- // istanbul ignore next
1468
1192
  for (const device of this.matterbridge.devices.array()) {
1469
1193
  if (device.serverNode)
1470
1194
  serverNodes.push(device.serverNode);
@@ -1488,15 +1212,8 @@ export class Frontend extends EventEmitter {
1488
1212
  values: [...serverNodes],
1489
1213
  })));
1490
1214
  delete Logger.destinations.diagnostic;
1491
- await wait(500); // Wait for the log to be written
1215
+ await wait(500);
1492
1216
  }
1493
- /**
1494
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1495
- *
1496
- * @param {WebSocket} client - The websocket client that sent the message.
1497
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1498
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1499
- */
1500
1217
  async wsMessageHandler(client, message) {
1501
1218
  let data;
1502
1219
  const sendResponse = (data) => {
@@ -1514,13 +1231,12 @@ export class Frontend extends EventEmitter {
1514
1231
  client.send(JSON.stringify(data));
1515
1232
  }
1516
1233
  else {
1517
- // istanbul ignore next cause is only a safety check
1518
1234
  this.log.error('Cannot send api response, client not connected');
1519
1235
  }
1520
1236
  };
1521
1237
  try {
1522
1238
  data = JSON.parse(message.toString());
1523
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1239
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1524
1240
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1525
1241
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1526
1242
  return;
@@ -1577,22 +1293,7 @@ export class Frontend extends EventEmitter {
1577
1293
  }
1578
1294
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1579
1295
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1580
- data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
1581
- /*
1582
- const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
1583
- if (plugin) {
1584
- this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1585
- await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
1586
- this.wssSendRestartRequired();
1587
- this.wssSendRefreshRequired('plugins');
1588
- this.wssSendRefreshRequired('devices');
1589
- this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1590
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1591
- } else {
1592
- this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1593
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1594
- }
1595
- */
1296
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1596
1297
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1597
1298
  if (plugin) {
1598
1299
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1605,7 +1306,7 @@ export class Frontend extends EventEmitter {
1605
1306
  this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1606
1307
  return;
1607
1308
  })
1608
- .catch(/* istanbul ignore next */ (_error) => { });
1309
+ .catch((_error) => { });
1609
1310
  }
1610
1311
  else {
1611
1312
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
@@ -1619,10 +1320,6 @@ export class Frontend extends EventEmitter {
1619
1320
  }
1620
1321
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1621
1322
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1622
- /*
1623
- await this.server.fetch({ type: 'plugins_shutdown', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName, reason: 'The plugin has been removed.', removeAllDevices: true } }, 5000);
1624
- await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
1625
- */
1626
1323
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1627
1324
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1628
1325
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1650,10 +1347,8 @@ export class Frontend extends EventEmitter {
1650
1347
  this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
1651
1348
  setImmediate(async () => {
1652
1349
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
1653
- // @ts-expect-error Accessing private method
1654
1350
  if (plugin.serverNode)
1655
1351
  await this.matterbridge.startServerNode(plugin.serverNode);
1656
- // @ts-expect-error Accessing private method
1657
1352
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1658
1353
  await this.matterbridge.startServerNode(device.serverNode);
1659
1354
  this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
@@ -1669,11 +1364,9 @@ export class Frontend extends EventEmitter {
1669
1364
  }
1670
1365
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1671
1366
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
1672
- // @ts-expect-error Accessing private method
1673
1367
  await this.matterbridge.stopServerNode(device.serverNode);
1674
1368
  device.serverNode = undefined;
1675
1369
  }
1676
- // @ts-expect-error Accessing private method
1677
1370
  if (plugin.serverNode)
1678
1371
  await this.matterbridge.stopServerNode(plugin.serverNode);
1679
1372
  plugin.serverNode = undefined;
@@ -1692,37 +1385,30 @@ export class Frontend extends EventEmitter {
1692
1385
  this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
1693
1386
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1694
1387
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1695
- // Stop server nodes
1696
1388
  if (plugin.serverNode) {
1697
- // @ts-expect-error Accessing private method
1698
1389
  await this.matterbridge.stopServerNode(plugin.serverNode);
1699
1390
  plugin.serverNode = undefined;
1700
1391
  }
1701
1392
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
1702
- // @ts-expect-error Accessing private method
1703
1393
  if (device.serverNode)
1704
1394
  await this.matterbridge.stopServerNode(device.serverNode);
1705
1395
  device.serverNode = undefined;
1706
1396
  this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
1707
1397
  this.matterbridge.devices.remove(device);
1708
1398
  }
1709
- // @ts-expect-error Accessing private method
1710
1399
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1711
1400
  await this.matterbridge.createDynamicPlugin(plugin);
1712
1401
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1713
- plugin.restartRequired = false; // Reset plugin restartRequired
1402
+ plugin.restartRequired = false;
1714
1403
  let needRestart = 0;
1715
1404
  for (const plugin of this.matterbridge.plugins) {
1716
1405
  if (plugin.restartRequired)
1717
1406
  needRestart++;
1718
1407
  }
1719
1408
  if (needRestart === 0)
1720
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1721
- // Start server nodes
1722
- // @ts-expect-error Accessing private method
1409
+ this.wssSendRestartNotRequired(true);
1723
1410
  if (plugin.serverNode)
1724
1411
  await this.matterbridge.startServerNode(plugin.serverNode);
1725
- // @ts-expect-error Accessing private method
1726
1412
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1727
1413
  await this.matterbridge.startServerNode(device.serverNode);
1728
1414
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1926,7 +1612,6 @@ export class Frontend extends EventEmitter {
1926
1612
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
1927
1613
  return;
1928
1614
  }
1929
- // istanbul ignore next
1930
1615
  const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1931
1616
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
1932
1617
  }
@@ -1940,7 +1625,6 @@ export class Frontend extends EventEmitter {
1940
1625
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
1941
1626
  return;
1942
1627
  }
1943
- // istanbul ignore next
1944
1628
  const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1945
1629
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
1946
1630
  }
@@ -1992,22 +1676,22 @@ export class Frontend extends EventEmitter {
1992
1676
  if (isValidString(data.params.value, 4)) {
1993
1677
  this.log.debug('Matterbridge logger level:', data.params.value);
1994
1678
  if (data.params.value === 'Debug') {
1995
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1679
+ await this.matterbridge.setLogLevel("debug");
1996
1680
  }
1997
1681
  else if (data.params.value === 'Info') {
1998
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1682
+ await this.matterbridge.setLogLevel("info");
1999
1683
  }
2000
1684
  else if (data.params.value === 'Notice') {
2001
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1685
+ await this.matterbridge.setLogLevel("notice");
2002
1686
  }
2003
1687
  else if (data.params.value === 'Warn') {
2004
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1688
+ await this.matterbridge.setLogLevel("warn");
2005
1689
  }
2006
1690
  else if (data.params.value === 'Error') {
2007
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1691
+ await this.matterbridge.setLogLevel("error");
2008
1692
  }
2009
1693
  else if (data.params.value === 'Fatal') {
2010
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1694
+ await this.matterbridge.setLogLevel("fatal");
2011
1695
  }
2012
1696
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2013
1697
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -2018,7 +1702,6 @@ export class Frontend extends EventEmitter {
2018
1702
  this.log.debug('Matterbridge file log:', data.params.value);
2019
1703
  this.matterbridge.fileLogger = data.params.value;
2020
1704
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
2021
- // Create the file logger for matterbridge
2022
1705
  if (data.params.value)
2023
1706
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
2024
1707
  else
@@ -2048,12 +1731,11 @@ export class Frontend extends EventEmitter {
2048
1731
  Logger.level = MatterLogLevel.FATAL;
2049
1732
  }
2050
1733
  this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
2051
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
2052
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
2053
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
2054
- callbackLogLevel = "info" /* LogLevel.INFO */;
2055
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
2056
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1734
+ let callbackLogLevel = "notice";
1735
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1736
+ callbackLogLevel = "info";
1737
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1738
+ callbackLogLevel = "debug";
2057
1739
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
2058
1740
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
2059
1741
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -2105,7 +1787,6 @@ export class Frontend extends EventEmitter {
2105
1787
  }
2106
1788
  break;
2107
1789
  case 'setmatterport':
2108
- // eslint-disable-next-line no-case-declarations
2109
1790
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2110
1791
  if (isValidNumber(port, 5540, 5600)) {
2111
1792
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -2125,7 +1806,6 @@ export class Frontend extends EventEmitter {
2125
1806
  }
2126
1807
  break;
2127
1808
  case 'setmatterdiscriminator':
2128
- // eslint-disable-next-line no-case-declarations
2129
1809
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2130
1810
  if (isValidNumber(discriminator, 0, 4095)) {
2131
1811
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -2145,7 +1825,6 @@ export class Frontend extends EventEmitter {
2145
1825
  }
2146
1826
  break;
2147
1827
  case 'setmatterpasscode':
2148
- // eslint-disable-next-line no-case-declarations
2149
1828
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2150
1829
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
2151
1830
  this.matterbridge.passcode = passcode;
@@ -2191,19 +1870,15 @@ export class Frontend extends EventEmitter {
2191
1870
  return;
2192
1871
  }
2193
1872
  const config = plugin.configJson;
2194
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2195
1873
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2196
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2197
1874
  if (select === 'serial')
2198
1875
  this.log.info(`Selected device serial ${data.params.serial}`);
2199
1876
  if (select === 'name')
2200
1877
  this.log.info(`Selected device name ${data.params.name}`);
2201
1878
  if (config && select && (select === 'serial' || select === 'name')) {
2202
- // Remove postfix from the serial if it exists
2203
1879
  if (config.postfix) {
2204
1880
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2205
1881
  }
2206
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2207
1882
  if (isValidArray(config.whiteList, 1)) {
2208
1883
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2209
1884
  config.whiteList.push(data.params.serial);
@@ -2212,7 +1887,6 @@ export class Frontend extends EventEmitter {
2212
1887
  config.whiteList.push(data.params.name);
2213
1888
  }
2214
1889
  }
2215
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2216
1890
  if (isValidArray(config.blackList, 1)) {
2217
1891
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2218
1892
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2240,9 +1914,7 @@ export class Frontend extends EventEmitter {
2240
1914
  return;
2241
1915
  }
2242
1916
  const config = plugin.configJson;
2243
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2244
1917
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2245
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2246
1918
  if (select === 'serial')
2247
1919
  this.log.info(`Unselected device serial ${data.params.serial}`);
2248
1920
  if (select === 'name')
@@ -2251,7 +1923,6 @@ export class Frontend extends EventEmitter {
2251
1923
  if (config.postfix) {
2252
1924
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2253
1925
  }
2254
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2255
1926
  if (isValidArray(config.whiteList, 1)) {
2256
1927
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2257
1928
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2260,7 +1931,6 @@ export class Frontend extends EventEmitter {
2260
1931
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2261
1932
  }
2262
1933
  }
2263
- // Add the serial to the blackList
2264
1934
  if (isValidArray(config.blackList)) {
2265
1935
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2266
1936
  config.blackList.push(data.params.serial);
@@ -2283,7 +1953,6 @@ export class Frontend extends EventEmitter {
2283
1953
  }
2284
1954
  }
2285
1955
  else {
2286
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2287
1956
  const localData = data;
2288
1957
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2289
1958
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2293,46 +1962,23 @@ export class Frontend extends EventEmitter {
2293
1962
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2294
1963
  }
2295
1964
  }
2296
- /**
2297
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2298
- *
2299
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2300
- * @param {string} time - The time string of the message
2301
- * @param {string} name - The logger name of the message
2302
- * @param {string} message - The content of the message.
2303
- *
2304
- * @remarks
2305
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2306
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2307
- * The function sends the message to all connected clients.
2308
- */
2309
1965
  wssSendLogMessage(level, time, name, message) {
2310
1966
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2311
1967
  return;
2312
1968
  if (!level || !time || !name || !message)
2313
1969
  return;
2314
- // Remove ANSI escape codes from the message
2315
- // eslint-disable-next-line no-control-regex
2316
1970
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2317
- // Remove leading asterisks from the message
2318
1971
  message = message.replace(/^\*+/, '');
2319
- // Replace all occurrences of \t and \n
2320
1972
  message = message.replace(/[\t\n]/g, '');
2321
- // Remove non-printable characters
2322
- // eslint-disable-next-line no-control-regex
2323
1973
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2324
- // Replace all occurrences of \" with "
2325
1974
  message = message.replace(/\\"/g, '"');
2326
- // Define the maximum allowed length for continuous characters without a space
2327
1975
  const maxContinuousLength = 100;
2328
1976
  const keepStartLength = 20;
2329
1977
  const keepEndLength = 20;
2330
- // Split the message into words
2331
1978
  if (level !== 'spawn') {
2332
1979
  message = message
2333
1980
  .split(' ')
2334
1981
  .map((word) => {
2335
- // If the word length exceeds the max continuous length, insert spaces and truncate
2336
1982
  if (word.length > maxContinuousLength) {
2337
1983
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2338
1984
  }
@@ -2340,34 +1986,14 @@ export class Frontend extends EventEmitter {
2340
1986
  })
2341
1987
  .join(' ');
2342
1988
  }
2343
- // Send the message to all connected clients
2344
1989
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2345
1990
  }
2346
- /**
2347
- * Sends a need to refresh WebSocket message to all connected clients.
2348
- *
2349
- * @param {string} changed - The changed value.
2350
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2351
- * possible values for changed:
2352
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2353
- * - 'plugins'
2354
- * - 'devices'
2355
- * - 'matter' with param 'matter' (QRDiv component)
2356
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2357
- */
2358
1991
  wssSendRefreshRequired(changed, params) {
2359
1992
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2360
1993
  return;
2361
1994
  this.log.debug('Sending a refresh required message to all connected clients');
2362
- // Send the message to all connected clients
2363
1995
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2364
1996
  }
2365
- /**
2366
- * Sends a need to restart WebSocket message to all connected clients.
2367
- *
2368
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2369
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2370
- */
2371
1997
  wssSendRestartRequired(snackbar = true, fixed = false) {
2372
1998
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2373
1999
  return;
@@ -2376,14 +2002,8 @@ export class Frontend extends EventEmitter {
2376
2002
  this.matterbridge.fixedRestartRequired = fixed;
2377
2003
  if (snackbar === true)
2378
2004
  this.wssSendSnackbarMessage(`Restart required`, 0);
2379
- // Send the message to all connected clients
2380
2005
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2381
2006
  }
2382
- /**
2383
- * Sends a no need to restart WebSocket message to all connected clients.
2384
- *
2385
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2386
- */
2387
2007
  wssSendRestartNotRequired(snackbar = true) {
2388
2008
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2389
2009
  return;
@@ -2391,145 +2011,64 @@ export class Frontend extends EventEmitter {
2391
2011
  this.matterbridge.restartRequired = false;
2392
2012
  if (snackbar === true)
2393
2013
  this.wssSendCloseSnackbarMessage(`Restart required`);
2394
- // Send the message to all connected clients
2395
2014
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2396
2015
  }
2397
- /**
2398
- * Sends a need to update WebSocket message to all connected clients.
2399
- *
2400
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2401
- */
2402
2016
  wssSendUpdateRequired(devVersion = false) {
2403
2017
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2404
2018
  return;
2405
2019
  this.log.debug('Sending an update required message to all connected clients');
2406
2020
  this.matterbridge.updateRequired = true;
2407
- // Send the message to all connected clients
2408
2021
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2409
2022
  }
2410
- /**
2411
- * Sends a cpu update message to all connected clients.
2412
- *
2413
- * @param {number} cpuUsage - The CPU usage percentage to send.
2414
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2415
- */
2416
2023
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2417
2024
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2418
2025
  return;
2419
- // istanbul ignore else
2420
2026
  if (hasParameter('debug'))
2421
2027
  this.log.debug('Sending a cpu update message to all connected clients');
2422
- // Send the message to all connected clients
2423
2028
  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 } });
2424
2029
  }
2425
- /**
2426
- * Sends a memory update message to all connected clients.
2427
- *
2428
- * @param {string} totalMemory - The total memory in bytes.
2429
- * @param {string} freeMemory - The free memory in bytes.
2430
- * @param {string} rss - The resident set size in bytes.
2431
- * @param {string} heapTotal - The total heap memory in bytes.
2432
- * @param {string} heapUsed - The used heap memory in bytes.
2433
- * @param {string} external - The external memory in bytes.
2434
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2435
- */
2436
2030
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2437
2031
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2438
2032
  return;
2439
- // istanbul ignore else
2440
2033
  if (hasParameter('debug'))
2441
2034
  this.log.debug('Sending a memory update message to all connected clients');
2442
- // Send the message to all connected clients
2443
2035
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2444
2036
  }
2445
- /**
2446
- * Sends an uptime update message to all connected clients.
2447
- *
2448
- * @param {string} systemUptime - The system uptime in a human-readable format.
2449
- * @param {string} processUptime - The process uptime in a human-readable format.
2450
- */
2451
2037
  wssSendUptimeUpdate(systemUptime, processUptime) {
2452
2038
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2453
2039
  return;
2454
- // istanbul ignore else
2455
2040
  if (hasParameter('debug'))
2456
2041
  this.log.debug('Sending a uptime update message to all connected clients');
2457
- // Send the message to all connected clients
2458
2042
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2459
2043
  }
2460
- /**
2461
- * Sends an open snackbar message to all connected clients.
2462
- *
2463
- * @param {string} message - The message to send.
2464
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2465
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2466
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2467
- *
2468
- * @remarks
2469
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2470
- */
2471
2044
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2472
2045
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2473
2046
  return;
2474
2047
  this.log.debug('Sending a snackbar message to all connected clients');
2475
- // Send the message to all connected clients
2476
2048
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2477
2049
  }
2478
- /**
2479
- * Sends a close snackbar message to all connected clients.
2480
- * It will close the snackbar message with the same message and timeout = 0.
2481
- *
2482
- * @param {string} message - The message to send.
2483
- */
2484
2050
  wssSendCloseSnackbarMessage(message) {
2485
2051
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2486
2052
  return;
2487
2053
  this.log.debug('Sending a close snackbar message to all connected clients');
2488
- // Send the message to all connected clients
2489
2054
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2490
2055
  }
2491
- /**
2492
- * Sends an attribute update message to all connected WebSocket clients.
2493
- *
2494
- * @param {string | undefined} plugin - The name of the plugin.
2495
- * @param {string | undefined} serialNumber - The serial number of the device.
2496
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2497
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2498
- * @param {string} id - The endpoint id where the attribute belongs.
2499
- * @param {string} cluster - The cluster name where the attribute belongs.
2500
- * @param {string} attribute - The name of the attribute that changed.
2501
- * @param {number | string | boolean} value - The new value of the attribute.
2502
- *
2503
- * @remarks
2504
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2505
- * with the updated attribute information.
2506
- */
2507
2056
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2508
2057
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2509
2058
  return;
2510
2059
  this.log.debug('Sending an attribute update message to all connected clients');
2511
- // Send the message to all connected clients
2512
2060
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2513
2061
  }
2514
- /**
2515
- * Sends a message to all connected clients.
2516
- * This is an helper function to send a broadcast message to all connected clients.
2517
- *
2518
- * @param {WsMessageBroadcast} msg - The message to send.
2519
- */
2520
2062
  wssBroadcastMessage(msg) {
2521
2063
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2522
2064
  return;
2523
- // Send the message to all connected clients
2524
2065
  const stringifiedMsg = JSON.stringify(msg);
2525
2066
  if (msg.method !== 'log')
2526
2067
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2527
2068
  this.webSocketServer?.clients.forEach((client) => {
2528
- // istanbul ignore else
2529
2069
  if (client.readyState === client.OPEN) {
2530
2070
  client.send(stringifiedMsg);
2531
2071
  }
2532
2072
  });
2533
2073
  }
2534
2074
  }
2535
- //# sourceMappingURL=frontend.js.map