@matterbridge/core 3.5.3 → 3.5.4-dev-20260211-520e349

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 (294) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.d.ts +0 -24
  3. package/dist/cli.js +1 -97
  4. package/dist/cliEmitter.d.ts +0 -36
  5. package/dist/cliEmitter.js +0 -37
  6. package/dist/cliHistory.d.ts +0 -42
  7. package/dist/cliHistory.js +0 -38
  8. package/dist/clusters/export.d.ts +0 -1
  9. package/dist/clusters/export.js +0 -2
  10. package/dist/deviceManager.d.ts +0 -108
  11. package/dist/deviceManager.js +1 -114
  12. package/dist/devices/airConditioner.d.ts +0 -75
  13. package/dist/devices/airConditioner.js +0 -57
  14. package/dist/devices/basicVideoPlayer.d.ts +0 -58
  15. package/dist/devices/basicVideoPlayer.js +1 -56
  16. package/dist/devices/batteryStorage.d.ts +0 -43
  17. package/dist/devices/batteryStorage.js +1 -48
  18. package/dist/devices/castingVideoPlayer.d.ts +0 -63
  19. package/dist/devices/castingVideoPlayer.js +2 -65
  20. package/dist/devices/cooktop.d.ts +0 -55
  21. package/dist/devices/cooktop.js +0 -56
  22. package/dist/devices/dishwasher.d.ts +0 -55
  23. package/dist/devices/dishwasher.js +0 -57
  24. package/dist/devices/evse.d.ts +0 -57
  25. package/dist/devices/evse.js +10 -74
  26. package/dist/devices/export.d.ts +0 -1
  27. package/dist/devices/export.js +0 -5
  28. package/dist/devices/extractorHood.d.ts +0 -41
  29. package/dist/devices/extractorHood.js +0 -43
  30. package/dist/devices/heatPump.d.ts +0 -43
  31. package/dist/devices/heatPump.js +2 -50
  32. package/dist/devices/laundryDryer.d.ts +0 -58
  33. package/dist/devices/laundryDryer.js +3 -62
  34. package/dist/devices/laundryWasher.d.ts +0 -64
  35. package/dist/devices/laundryWasher.js +4 -70
  36. package/dist/devices/microwaveOven.d.ts +1 -77
  37. package/dist/devices/microwaveOven.js +5 -88
  38. package/dist/devices/oven.d.ts +0 -82
  39. package/dist/devices/oven.js +0 -85
  40. package/dist/devices/refrigerator.d.ts +0 -100
  41. package/dist/devices/refrigerator.js +0 -102
  42. package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
  43. package/dist/devices/roboticVacuumCleaner.js +9 -100
  44. package/dist/devices/solarPower.d.ts +0 -36
  45. package/dist/devices/solarPower.js +0 -38
  46. package/dist/devices/speaker.d.ts +0 -79
  47. package/dist/devices/speaker.js +0 -84
  48. package/dist/devices/temperatureControl.d.ts +0 -21
  49. package/dist/devices/temperatureControl.js +3 -24
  50. package/dist/devices/waterHeater.d.ts +0 -74
  51. package/dist/devices/waterHeater.js +2 -82
  52. package/dist/dgram/export.d.ts +0 -1
  53. package/dist/dgram/export.js +0 -1
  54. package/dist/export.d.ts +0 -23
  55. package/dist/export.js +0 -28
  56. package/dist/frontend.d.ts +0 -187
  57. package/dist/frontend.js +62 -581
  58. package/dist/helpers.d.ts +0 -43
  59. package/dist/helpers.js +0 -86
  60. package/dist/jestutils/export.d.ts +0 -1
  61. package/dist/jestutils/export.js +0 -1
  62. package/dist/jestutils/jestHelpers.d.ts +0 -259
  63. package/dist/jestutils/jestHelpers.js +15 -396
  64. package/dist/matter/behaviors.d.ts +0 -1
  65. package/dist/matter/behaviors.js +0 -2
  66. package/dist/matter/clusters.d.ts +0 -1
  67. package/dist/matter/clusters.js +0 -2
  68. package/dist/matter/devices.d.ts +0 -1
  69. package/dist/matter/devices.js +0 -2
  70. package/dist/matter/endpoints.d.ts +0 -1
  71. package/dist/matter/endpoints.js +0 -2
  72. package/dist/matter/export.d.ts +0 -1
  73. package/dist/matter/export.js +0 -2
  74. package/dist/matter/types.d.ts +0 -1
  75. package/dist/matter/types.js +0 -2
  76. package/dist/matterNode.d.ts +0 -258
  77. package/dist/matterNode.js +8 -356
  78. package/dist/matterbridge.d.ts +0 -389
  79. package/dist/matterbridge.js +48 -878
  80. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  81. package/dist/matterbridgeAccessoryPlatform.js +0 -50
  82. package/dist/matterbridgeBehaviors.d.ts +0 -24
  83. package/dist/matterbridgeBehaviors.js +5 -65
  84. package/dist/matterbridgeDeviceTypes.d.ts +0 -649
  85. package/dist/matterbridgeDeviceTypes.js +6 -673
  86. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  87. package/dist/matterbridgeDynamicPlatform.js +0 -50
  88. package/dist/matterbridgeEndpoint.d.ts +0 -1369
  89. package/dist/matterbridgeEndpoint.js +56 -1514
  90. package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
  91. package/dist/matterbridgeEndpointHelpers.js +20 -483
  92. package/dist/matterbridgeEndpointTypes.d.ts +0 -70
  93. package/dist/matterbridgeEndpointTypes.js +0 -25
  94. package/dist/matterbridgePlatform.d.ts +0 -434
  95. package/dist/matterbridgePlatform.js +1 -473
  96. package/dist/pluginManager.d.ts +0 -307
  97. package/dist/pluginManager.js +6 -346
  98. package/dist/spawn.d.ts +0 -32
  99. package/dist/spawn.js +1 -71
  100. package/dist/utils/export.d.ts +0 -1
  101. package/dist/utils/export.js +0 -1
  102. package/package.json +27 -6
  103. package/dist/cli.d.ts.map +0 -1
  104. package/dist/cli.js.map +0 -1
  105. package/dist/cliEmitter.d.ts.map +0 -1
  106. package/dist/cliEmitter.js.map +0 -1
  107. package/dist/cliHistory.d.ts.map +0 -1
  108. package/dist/cliHistory.js.map +0 -1
  109. package/dist/clusters/export.d.ts.map +0 -1
  110. package/dist/clusters/export.js.map +0 -1
  111. package/dist/crypto/attestationDecoder.d.ts +0 -180
  112. package/dist/crypto/attestationDecoder.d.ts.map +0 -1
  113. package/dist/crypto/attestationDecoder.js +0 -176
  114. package/dist/crypto/attestationDecoder.js.map +0 -1
  115. package/dist/crypto/declarationDecoder.d.ts +0 -72
  116. package/dist/crypto/declarationDecoder.d.ts.map +0 -1
  117. package/dist/crypto/declarationDecoder.js +0 -241
  118. package/dist/crypto/declarationDecoder.js.map +0 -1
  119. package/dist/crypto/extract/342/200/220cert/342/200/220extensions.d.ts +0 -9
  120. package/dist/crypto/extract/342/200/220cert/342/200/220extensions.d.ts.map +0 -1
  121. package/dist/crypto/extract/342/200/220cert/342/200/220extensions.js +0 -120
  122. package/dist/crypto/extract/342/200/220cert/342/200/220extensions.js.map +0 -1
  123. package/dist/crypto/read-extensions.d.ts +0 -2
  124. package/dist/crypto/read-extensions.d.ts.map +0 -1
  125. package/dist/crypto/read-extensions.js +0 -81
  126. package/dist/crypto/read-extensions.js.map +0 -1
  127. package/dist/crypto/testData.d.ts +0 -31
  128. package/dist/crypto/testData.d.ts.map +0 -1
  129. package/dist/crypto/testData.js +0 -131
  130. package/dist/crypto/testData.js.map +0 -1
  131. package/dist/crypto/walk-der.d.ts +0 -2
  132. package/dist/crypto/walk-der.d.ts.map +0 -1
  133. package/dist/crypto/walk-der.js +0 -165
  134. package/dist/crypto/walk-der.js.map +0 -1
  135. package/dist/deviceManager.d.ts.map +0 -1
  136. package/dist/deviceManager.js.map +0 -1
  137. package/dist/devices/airConditioner.d.ts.map +0 -1
  138. package/dist/devices/airConditioner.js.map +0 -1
  139. package/dist/devices/basicVideoPlayer.d.ts.map +0 -1
  140. package/dist/devices/basicVideoPlayer.js.map +0 -1
  141. package/dist/devices/batteryStorage.d.ts.map +0 -1
  142. package/dist/devices/batteryStorage.js.map +0 -1
  143. package/dist/devices/castingVideoPlayer.d.ts.map +0 -1
  144. package/dist/devices/castingVideoPlayer.js.map +0 -1
  145. package/dist/devices/cooktop.d.ts.map +0 -1
  146. package/dist/devices/cooktop.js.map +0 -1
  147. package/dist/devices/dishwasher.d.ts.map +0 -1
  148. package/dist/devices/dishwasher.js.map +0 -1
  149. package/dist/devices/evse.d.ts.map +0 -1
  150. package/dist/devices/evse.js.map +0 -1
  151. package/dist/devices/export.d.ts.map +0 -1
  152. package/dist/devices/export.js.map +0 -1
  153. package/dist/devices/extractorHood.d.ts.map +0 -1
  154. package/dist/devices/extractorHood.js.map +0 -1
  155. package/dist/devices/heatPump.d.ts.map +0 -1
  156. package/dist/devices/heatPump.js.map +0 -1
  157. package/dist/devices/laundryDryer.d.ts.map +0 -1
  158. package/dist/devices/laundryDryer.js.map +0 -1
  159. package/dist/devices/laundryWasher.d.ts.map +0 -1
  160. package/dist/devices/laundryWasher.js.map +0 -1
  161. package/dist/devices/microwaveOven.d.ts.map +0 -1
  162. package/dist/devices/microwaveOven.js.map +0 -1
  163. package/dist/devices/oven.d.ts.map +0 -1
  164. package/dist/devices/oven.js.map +0 -1
  165. package/dist/devices/refrigerator.d.ts.map +0 -1
  166. package/dist/devices/refrigerator.js.map +0 -1
  167. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  168. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  169. package/dist/devices/solarPower.d.ts.map +0 -1
  170. package/dist/devices/solarPower.js.map +0 -1
  171. package/dist/devices/speaker.d.ts.map +0 -1
  172. package/dist/devices/speaker.js.map +0 -1
  173. package/dist/devices/temperatureControl.d.ts.map +0 -1
  174. package/dist/devices/temperatureControl.js.map +0 -1
  175. package/dist/devices/waterHeater.d.ts.map +0 -1
  176. package/dist/devices/waterHeater.js.map +0 -1
  177. package/dist/dgram/export.d.ts.map +0 -1
  178. package/dist/dgram/export.js.map +0 -1
  179. package/dist/export.d.ts.map +0 -1
  180. package/dist/export.js.map +0 -1
  181. package/dist/frontend.d.ts.map +0 -1
  182. package/dist/frontend.js.map +0 -1
  183. package/dist/helpers.d.ts.map +0 -1
  184. package/dist/helpers.js.map +0 -1
  185. package/dist/jestutils/export.d.ts.map +0 -1
  186. package/dist/jestutils/export.js.map +0 -1
  187. package/dist/jestutils/jestHelpers.d.ts.map +0 -1
  188. package/dist/jestutils/jestHelpers.js.map +0 -1
  189. package/dist/matter/behaviors.d.ts.map +0 -1
  190. package/dist/matter/behaviors.js.map +0 -1
  191. package/dist/matter/clusters.d.ts.map +0 -1
  192. package/dist/matter/clusters.js.map +0 -1
  193. package/dist/matter/devices.d.ts.map +0 -1
  194. package/dist/matter/devices.js.map +0 -1
  195. package/dist/matter/endpoints.d.ts.map +0 -1
  196. package/dist/matter/endpoints.js.map +0 -1
  197. package/dist/matter/export.d.ts.map +0 -1
  198. package/dist/matter/export.js.map +0 -1
  199. package/dist/matter/types.d.ts.map +0 -1
  200. package/dist/matter/types.js.map +0 -1
  201. package/dist/matterNode.d.ts.map +0 -1
  202. package/dist/matterNode.js.map +0 -1
  203. package/dist/matterbridge.d.ts.map +0 -1
  204. package/dist/matterbridge.js.map +0 -1
  205. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  206. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  207. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  208. package/dist/matterbridgeBehaviors.js.map +0 -1
  209. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  210. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  211. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  212. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  213. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  214. package/dist/matterbridgeEndpoint.js.map +0 -1
  215. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  216. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  217. package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
  218. package/dist/matterbridgeEndpointTypes.js.map +0 -1
  219. package/dist/matterbridgePlatform.d.ts.map +0 -1
  220. package/dist/matterbridgePlatform.js.map +0 -1
  221. package/dist/mb_coap.d.ts +0 -24
  222. package/dist/mb_coap.d.ts.map +0 -1
  223. package/dist/mb_coap.js +0 -89
  224. package/dist/mb_coap.js.map +0 -1
  225. package/dist/mb_health.d.ts +0 -77
  226. package/dist/mb_health.d.ts.map +0 -1
  227. package/dist/mb_health.js +0 -147
  228. package/dist/mb_health.js.map +0 -1
  229. package/dist/mb_mdns.d.ts +0 -24
  230. package/dist/mb_mdns.d.ts.map +0 -1
  231. package/dist/mb_mdns.js +0 -285
  232. package/dist/mb_mdns.js.map +0 -1
  233. package/dist/pluginManager.d.ts.map +0 -1
  234. package/dist/pluginManager.js.map +0 -1
  235. package/dist/spawn.d.ts.map +0 -1
  236. package/dist/spawn.js.map +0 -1
  237. package/dist/utils/export.d.ts.map +0 -1
  238. package/dist/utils/export.js.map +0 -1
  239. package/dist/workers/brand.d.ts +0 -25
  240. package/dist/workers/brand.d.ts.map +0 -1
  241. package/dist/workers/brand.extend.d.ts +0 -10
  242. package/dist/workers/brand.extend.d.ts.map +0 -1
  243. package/dist/workers/brand.extend.js +0 -15
  244. package/dist/workers/brand.extend.js.map +0 -1
  245. package/dist/workers/brand.invalid.d.ts +0 -9
  246. package/dist/workers/brand.invalid.d.ts.map +0 -1
  247. package/dist/workers/brand.invalid.js +0 -19
  248. package/dist/workers/brand.invalid.js.map +0 -1
  249. package/dist/workers/brand.js +0 -67
  250. package/dist/workers/brand.js.map +0 -1
  251. package/dist/workers/clusterTypes.d.ts +0 -47
  252. package/dist/workers/clusterTypes.d.ts.map +0 -1
  253. package/dist/workers/clusterTypes.js +0 -57
  254. package/dist/workers/clusterTypes.js.map +0 -1
  255. package/dist/workers/frontendWorker.d.ts +0 -2
  256. package/dist/workers/frontendWorker.d.ts.map +0 -1
  257. package/dist/workers/frontendWorker.js +0 -90
  258. package/dist/workers/frontendWorker.js.map +0 -1
  259. package/dist/workers/helloWorld.d.ts +0 -2
  260. package/dist/workers/helloWorld.d.ts.map +0 -1
  261. package/dist/workers/helloWorld.js +0 -135
  262. package/dist/workers/helloWorld.js.map +0 -1
  263. package/dist/workers/matterWorker.d.ts +0 -2
  264. package/dist/workers/matterWorker.d.ts.map +0 -1
  265. package/dist/workers/matterWorker.js +0 -104
  266. package/dist/workers/matterWorker.js.map +0 -1
  267. package/dist/workers/matterbridgeWorker.d.ts +0 -2
  268. package/dist/workers/matterbridgeWorker.d.ts.map +0 -1
  269. package/dist/workers/matterbridgeWorker.js +0 -75
  270. package/dist/workers/matterbridgeWorker.js.map +0 -1
  271. package/dist/workers/messageLab.d.ts +0 -134
  272. package/dist/workers/messageLab.d.ts.map +0 -1
  273. package/dist/workers/messageLab.js +0 -129
  274. package/dist/workers/messageLab.js.map +0 -1
  275. package/dist/workers/testWorker.d.ts +0 -2
  276. package/dist/workers/testWorker.d.ts.map +0 -1
  277. package/dist/workers/testWorker.js +0 -45
  278. package/dist/workers/testWorker.js.map +0 -1
  279. package/dist/workers/usage.d.ts +0 -19
  280. package/dist/workers/usage.d.ts.map +0 -1
  281. package/dist/workers/usage.js +0 -140
  282. package/dist/workers/usage.js.map +0 -1
  283. package/dist/workers/workerManager.d.ts +0 -115
  284. package/dist/workers/workerManager.d.ts.map +0 -1
  285. package/dist/workers/workerManager.js +0 -464
  286. package/dist/workers/workerManager.js.map +0 -1
  287. package/dist/workers/workerServer.d.ts +0 -126
  288. package/dist/workers/workerServer.d.ts.map +0 -1
  289. package/dist/workers/workerServer.js +0 -340
  290. package/dist/workers/workerServer.js.map +0 -1
  291. package/dist/workers/workerTypes.d.ts +0 -23
  292. package/dist/workers/workerTypes.d.ts.map +0 -1
  293. package/dist/workers/workerTypes.js +0 -3
  294. package/dist/workers/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 '@matterbridge/types';
42
15
  import { BroadcastServer } from '@matterbridge/thread';
@@ -63,8 +36,8 @@ export class Frontend extends EventEmitter {
63
36
  this.log = new AnsiLogger({
64
37
  logName: 'Frontend',
65
38
  logNameColor: '\x1b[38;5;97m',
66
- logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */,
67
- logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */,
39
+ logTimestampFormat: 4,
40
+ logLevel: hasParameter('debug') ? "debug" : "info",
68
41
  });
69
42
  this.server = new BroadcastServer('frontend', this.log);
70
43
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -75,7 +48,6 @@ export class Frontend extends EventEmitter {
75
48
  }
76
49
  async msgHandler(msg) {
77
50
  if (this.server.isWorkerRequest(msg)) {
78
- // istanbul ignore else
79
51
  if (this.verbose)
80
52
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
81
53
  switch (msg.type) {
@@ -127,13 +99,11 @@ export class Frontend extends EventEmitter {
127
99
  this.server.respond({ ...msg, result: { success: true } });
128
100
  break;
129
101
  default:
130
- // istanbul ignore next
131
102
  if (this.verbose)
132
103
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
133
104
  }
134
105
  }
135
106
  if (this.server.isWorkerResponse(msg) && msg.result) {
136
- // istanbul ignore next
137
107
  if (this.verbose)
138
108
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
139
109
  switch (msg.type) {
@@ -177,55 +147,23 @@ export class Frontend extends EventEmitter {
177
147
  this.port = port;
178
148
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
179
149
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
180
- // Initialize multer with the upload directory
181
150
  const multer = await import('multer');
182
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
151
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
183
152
  const upload = multer.default({ dest: uploadDir });
184
- // Create the express app that serves the frontend
185
153
  const express = await import('express');
186
154
  this.expressApp = express.default();
187
- // Inject logging/debug wrapper for route/middleware registration
188
- /*
189
- const methods = ['get', 'post', 'put', 'delete', 'use'];
190
- for (const method of methods) {
191
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
- const original = (this.expressApp as any)[method].bind(this.expressApp);
193
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
195
- try {
196
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
197
- return original(path, ...rest);
198
- } catch (err) {
199
- console.error(`[ERROR] Failed to register route: ${path}`);
200
- throw err;
201
- }
202
- };
203
- }
204
- */
205
- // Log all requests to the server for debugging
206
- /*
207
- this.expressApp.use((req, res, next) => {
208
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
209
- next();
210
- });
211
- */
212
- // Serve static files from 'frontend/build' directory
213
155
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'apps', 'frontend', 'build')));
214
- // Create a WebSocket server and attach it to the http or https server
215
156
  this.log.debug(`Creating WebSocketServer...`);
216
157
  const ws = await import('ws');
217
158
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
218
159
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
219
160
  this.webSocketServer.on('connection', (ws, request) => {
220
161
  const clientIp = request.socket.remoteAddress;
221
- // Set the global logger callback for the WebSocketServer
222
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
223
- // istanbul ignore else
224
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
225
- callbackLogLevel = "info" /* LogLevel.INFO */;
226
- // istanbul ignore else
227
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
228
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
162
+ let callbackLogLevel = "notice";
163
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
164
+ callbackLogLevel = "info";
165
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
166
+ callbackLogLevel = "debug";
229
167
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
230
168
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
231
169
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -241,34 +179,23 @@ export class Frontend extends EventEmitter {
241
179
  });
242
180
  ws.on('close', () => {
243
181
  this.log.info('WebSocket client disconnected');
244
- // istanbul ignore else
245
182
  if (this.webSocketServer?.clients.size === 0) {
246
183
  AnsiLogger.setGlobalCallback(undefined);
247
184
  this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
248
185
  this.authClients = [];
249
186
  }
250
187
  });
251
- // istanbul ignore next
252
188
  ws.on('error', (error) => {
253
- // istanbul ignore next
254
189
  this.log.error(`WebSocket client error: ${error}`);
255
190
  });
256
191
  });
257
192
  this.webSocketServer.on('close', () => {
258
193
  this.log.debug(`WebSocketServer closed`);
259
194
  });
260
- /* With { noServer: true } it never fires
261
- this.webSocketServer.on('listening', () => {
262
- this.log.info(`The WebSocketServer is listening`);
263
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
264
- });
265
- */
266
- // istanbul ignore next
267
195
  this.webSocketServer.on('error', (ws, error) => {
268
196
  this.log.error(`WebSocketServer error: ${error}`);
269
197
  });
270
198
  if (!hasParameter('ssl')) {
271
- // Create an HTTP server and attach the express app
272
199
  const http = await import('node:http');
273
200
  try {
274
201
  this.log.debug(`Creating HTTP server...`);
@@ -279,54 +206,33 @@ export class Frontend extends EventEmitter {
279
206
  this.emit('server_error', error);
280
207
  return;
281
208
  }
282
- // Listen on the specified port
283
- if (hasParameter('ingress')) {
284
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
285
- this.httpServer.listen(this.port, '0.0.0.0', () => {
286
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
287
- this.listening = true;
288
- this.emit('server_listening', 'http', this.port, '0.0.0.0');
289
- });
290
- }
291
- else {
292
- // We listen to all available addresses
293
- this.httpServer.listen(this.port, getParameter('bind'), () => {
294
- const addr = this.httpServer?.address();
295
- // istanbul ignore else
296
- if (addr && typeof addr !== 'string') {
297
- this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
298
- }
299
- // istanbul ignore else
300
- if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
301
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
302
- // istanbul ignore else
303
- if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
304
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
305
- this.listening = true;
306
- this.emit('server_listening', 'http', this.port);
307
- });
308
- }
209
+ this.httpServer.listen(this.port, getParameter('bind'), () => {
210
+ const addr = this.httpServer?.address();
211
+ if (addr && typeof addr !== 'string') {
212
+ this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
213
+ }
214
+ if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
215
+ this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
216
+ if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
217
+ this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
218
+ this.listening = true;
219
+ this.emit('server_listening', 'http', this.port);
220
+ });
309
221
  this.httpServer.on('upgrade', async (req, socket, head) => {
310
222
  try {
311
- // Only proceed for real WebSocket upgrades
312
- // istanbul ignore next cause is only a safety check
313
223
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
314
224
  this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
315
225
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
316
226
  return socket.destroy();
317
227
  }
318
- // Build a URL so we can read ?password=...
319
228
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
320
- // Validate WebSocket password
321
229
  const password = url.searchParams.get('password') ?? '';
322
230
  if (password !== this.storedPassword) {
323
231
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
324
232
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
325
233
  return socket.destroy();
326
234
  }
327
- // Complete the WebSocket handshake
328
235
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
329
- // istanbul ignore else
330
236
  if (req.socket.remoteAddress)
331
237
  this.authClients.push(req.socket.remoteAddress);
332
238
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
@@ -334,7 +240,6 @@ export class Frontend extends EventEmitter {
334
240
  });
335
241
  }
336
242
  catch (err) {
337
- /* istanbul ignore next: only triggered on unexpected internal error */
338
243
  {
339
244
  inspectError(this.log, 'WebSocket upgrade error:', err);
340
245
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -357,7 +262,6 @@ export class Frontend extends EventEmitter {
357
262
  });
358
263
  }
359
264
  else {
360
- // SSL is enabled, load the certificate and the private key
361
265
  let cert;
362
266
  let key;
363
267
  let ca;
@@ -367,7 +271,6 @@ export class Frontend extends EventEmitter {
367
271
  let httpsServerOptions = {};
368
272
  const fs = await import('node:fs');
369
273
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
370
- // Load the p12 certificate and the passphrase
371
274
  try {
372
275
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
373
276
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -379,7 +282,7 @@ export class Frontend extends EventEmitter {
379
282
  }
380
283
  try {
381
284
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
382
- passphrase = passphrase.trim(); // Ensure no extra characters
285
+ passphrase = passphrase.trim();
383
286
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
384
287
  }
385
288
  catch (error) {
@@ -390,7 +293,6 @@ export class Frontend extends EventEmitter {
390
293
  httpsServerOptions = { pfx, passphrase };
391
294
  }
392
295
  else {
393
- // 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.
394
296
  try {
395
297
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
396
298
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -420,10 +322,9 @@ export class Frontend extends EventEmitter {
420
322
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
421
323
  }
422
324
  if (hasParameter('mtls')) {
423
- httpsServerOptions.requestCert = true; // Request client certificate
424
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
325
+ httpsServerOptions.requestCert = true;
326
+ httpsServerOptions.rejectUnauthorized = true;
425
327
  }
426
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
427
328
  const https = await import('node:https');
428
329
  try {
429
330
  this.log.debug(`Creating HTTPS server...`);
@@ -434,53 +335,32 @@ export class Frontend extends EventEmitter {
434
335
  this.emit('server_error', error);
435
336
  return;
436
337
  }
437
- // Listen on the specified port
438
- if (hasParameter('ingress')) {
439
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
440
- this.httpsServer.listen(this.port, '0.0.0.0', () => {
441
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
442
- this.listening = true;
443
- this.emit('server_listening', 'https', this.port, '0.0.0.0');
444
- });
445
- }
446
- else {
447
- // We listen to all available addresses
448
- this.httpsServer.listen(this.port, getParameter('bind'), () => {
449
- const addr = this.httpsServer?.address();
450
- // istanbul ignore else
451
- if (addr && typeof addr !== 'string') {
452
- this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
453
- }
454
- // istanbul ignore else
455
- if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
456
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
457
- // istanbul ignore else
458
- if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
459
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
460
- this.listening = true;
461
- this.emit('server_listening', 'https', this.port);
462
- });
463
- }
338
+ this.httpsServer.listen(this.port, getParameter('bind'), () => {
339
+ const addr = this.httpsServer?.address();
340
+ if (addr && typeof addr !== 'string') {
341
+ this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
342
+ }
343
+ if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
344
+ this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
345
+ if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
346
+ this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
347
+ this.listening = true;
348
+ this.emit('server_listening', 'https', this.port);
349
+ });
464
350
  this.httpsServer.on('upgrade', async (req, socket, head) => {
465
351
  try {
466
- // Only proceed for real WebSocket upgrades
467
- // istanbul ignore next cause is only a safety check
468
352
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
469
353
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
470
354
  return socket.destroy();
471
355
  }
472
- // Build a URL so we can read ?password=...
473
356
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
474
- // Validate WebSocket password
475
357
  const password = url.searchParams.get('password') ?? '';
476
358
  if (password !== this.storedPassword) {
477
359
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
478
360
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
479
361
  return socket.destroy();
480
362
  }
481
- // Complete the WebSocket handshake
482
363
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
483
- // istanbul ignore else
484
364
  if (req.socket.remoteAddress)
485
365
  this.authClients.push(req.socket.remoteAddress);
486
366
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
@@ -488,7 +368,6 @@ export class Frontend extends EventEmitter {
488
368
  });
489
369
  }
490
370
  catch (err) {
491
- /* istanbul ignore next: only triggered on unexpected internal error */
492
371
  {
493
372
  inspectError(this.log, 'WebSocket upgrade error:', err);
494
373
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -510,7 +389,6 @@ export class Frontend extends EventEmitter {
510
389
  return;
511
390
  });
512
391
  }
513
- // Subscribe to cli events
514
392
  cliEmitter.removeAllListeners();
515
393
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
516
394
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -521,8 +399,6 @@ export class Frontend extends EventEmitter {
521
399
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
522
400
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
523
401
  });
524
- // Endpoint to validate login code
525
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
526
402
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
527
403
  const { password } = req.body;
528
404
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -537,20 +413,17 @@ export class Frontend extends EventEmitter {
537
413
  res.json({ valid: false });
538
414
  }
539
415
  });
540
- // Endpoint to provide health check for docker
541
416
  this.expressApp.get('/health', (req, res) => {
542
417
  this.log.debug('Express received /health');
543
418
  const healthStatus = {
544
- status: 'ok', // Indicate service is healthy
545
- uptime: process.uptime(), // Server uptime in seconds
546
- timestamp: new Date().toISOString(), // Current timestamp
419
+ status: 'ok',
420
+ uptime: process.uptime(),
421
+ timestamp: new Date().toISOString(),
547
422
  };
548
423
  res.status(200).json(healthStatus);
549
424
  });
550
- // Endpoint to provide memory usage details
551
425
  this.expressApp.get('/memory', async (req, res) => {
552
426
  this.log.debug('Express received /memory');
553
- // Memory usage from process
554
427
  const memoryUsageRaw = process.memoryUsage();
555
428
  const memoryUsage = {
556
429
  rss: formatBytes(memoryUsageRaw.rss),
@@ -559,13 +432,10 @@ export class Frontend extends EventEmitter {
559
432
  external: formatBytes(memoryUsageRaw.external),
560
433
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
561
434
  };
562
- // V8 heap statistics
563
435
  const { default: v8 } = await import('node:v8');
564
436
  const heapStatsRaw = v8.getHeapStatistics();
565
437
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
566
- // Format heapStats
567
438
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
568
- // Format heapSpaces
569
439
  const heapSpaces = heapSpacesRaw.map((space) => ({
570
440
  ...space,
571
441
  space_size: formatBytes(space.space_size),
@@ -584,28 +454,24 @@ export class Frontend extends EventEmitter {
584
454
  };
585
455
  res.status(200).json(memoryReport);
586
456
  });
587
- // Endpoint to provide settings
588
457
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
589
458
  this.log.debug('The frontend sent /api/settings');
590
459
  if (!this.validateReq(req, res))
591
460
  return;
592
461
  res.json(await this.getApiSettings());
593
462
  });
594
- // Endpoint to provide plugins
595
463
  this.expressApp.get('/api/plugins', async (req, res) => {
596
464
  this.log.debug('The frontend sent /api/plugins');
597
465
  if (!this.validateReq(req, res))
598
466
  return;
599
467
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
600
468
  });
601
- // Endpoint to provide devices
602
469
  this.expressApp.get('/api/devices', async (req, res) => {
603
470
  this.log.debug('The frontend sent /api/devices');
604
471
  if (!this.validateReq(req, res))
605
472
  return;
606
473
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
607
474
  });
608
- // Endpoint to view the matterbridge log
609
475
  this.expressApp.get('/api/view-mblog', async (req, res) => {
610
476
  this.log.debug('The frontend sent /api/view-mblog');
611
477
  if (!this.validateReq(req, res))
@@ -621,7 +487,6 @@ export class Frontend extends EventEmitter {
621
487
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
622
488
  }
623
489
  });
624
- // Endpoint to view the matter.js log
625
490
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
626
491
  this.log.debug('The frontend sent /api/view-mjlog');
627
492
  if (!this.validateReq(req, res))
@@ -637,7 +502,6 @@ export class Frontend extends EventEmitter {
637
502
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
638
503
  }
639
504
  });
640
- // Endpoint to view the diagnostic.log
641
505
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
642
506
  this.log.debug('The frontend sent /api/view-diagnostic');
643
507
  if (!this.validateReq(req, res))
@@ -650,13 +514,10 @@ export class Frontend extends EventEmitter {
650
514
  res.send(data.slice(29));
651
515
  }
652
516
  catch (error) {
653
- // istanbul ignore next
654
517
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
655
- // istanbul ignore next
656
518
  res.status(500).send('Error reading diagnostic log file.');
657
519
  }
658
520
  });
659
- // Endpoint to download the diagnostic.log
660
521
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
661
522
  this.log.debug(`The frontend sent /api/download-diagnostic`);
662
523
  if (!this.validateReq(req, res))
@@ -669,19 +530,16 @@ export class Frontend extends EventEmitter {
669
530
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
670
531
  }
671
532
  catch (error) {
672
- // istanbul ignore next
673
533
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
674
534
  }
675
535
  res.type('text/plain');
676
536
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
677
- /* istanbul ignore if */
678
537
  if (error) {
679
538
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
680
539
  res.status(500).send('Error downloading the diagnostic log file');
681
540
  }
682
541
  });
683
542
  });
684
- // Endpoint to view the history.html
685
543
  this.expressApp.get('/api/viewhistory', async (req, res) => {
686
544
  this.log.debug('The frontend sent /api/viewhistory');
687
545
  if (!this.validateReq(req, res))
@@ -697,7 +555,6 @@ export class Frontend extends EventEmitter {
697
555
  res.status(500).send('Error reading history file.');
698
556
  }
699
557
  });
700
- // Endpoint to download the history.html
701
558
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
702
559
  this.log.debug(`The frontend sent /api/downloadhistory`);
703
560
  if (!this.validateReq(req, res))
@@ -709,7 +566,6 @@ export class Frontend extends EventEmitter {
709
566
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
710
567
  res.type('text/plain');
711
568
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
712
- /* istanbul ignore if */
713
569
  if (error) {
714
570
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
715
571
  res.status(500).send('Error downloading history file');
@@ -721,7 +577,6 @@ export class Frontend extends EventEmitter {
721
577
  res.status(500).send('Error reading history file.');
722
578
  }
723
579
  });
724
- // Endpoint to view the shelly log
725
580
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
726
581
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
727
582
  if (!this.validateReq(req, res))
@@ -737,7 +592,6 @@ export class Frontend extends EventEmitter {
737
592
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
738
593
  }
739
594
  });
740
- // Endpoint to download the matterbridge log
741
595
  this.expressApp.get('/api/download-mblog', async (req, res) => {
742
596
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
743
597
  if (!this.validateReq(req, res))
@@ -754,14 +608,12 @@ export class Frontend extends EventEmitter {
754
608
  }
755
609
  res.type('text/plain');
756
610
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
757
- /* istanbul ignore if */
758
611
  if (error) {
759
612
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
760
613
  res.status(500).send('Error downloading the matterbridge log file');
761
614
  }
762
615
  });
763
616
  });
764
- // Endpoint to download the matter log
765
617
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
766
618
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
767
619
  if (!this.validateReq(req, res))
@@ -778,14 +630,12 @@ export class Frontend extends EventEmitter {
778
630
  }
779
631
  res.type('text/plain');
780
632
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
781
- /* istanbul ignore if */
782
633
  if (error) {
783
634
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
784
635
  res.status(500).send('Error downloading the matter log file');
785
636
  }
786
637
  });
787
638
  });
788
- // Endpoint to download the shelly log
789
639
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
790
640
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
791
641
  if (!this.validateReq(req, res))
@@ -802,103 +652,87 @@ export class Frontend extends EventEmitter {
802
652
  }
803
653
  res.type('text/plain');
804
654
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
805
- /* istanbul ignore if */
806
655
  if (error) {
807
656
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
808
657
  res.status(500).send('Error downloading Shelly system log file');
809
658
  }
810
659
  });
811
660
  });
812
- // Endpoint to download the matterbridge storage directory
813
661
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
814
662
  this.log.debug('The frontend sent /api/download-mbstorage');
815
663
  if (!this.validateReq(req, res))
816
664
  return;
817
665
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
818
666
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
819
- /* istanbul ignore if */
820
667
  if (error) {
821
668
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
822
669
  res.status(500).send('Error downloading the matterbridge storage file');
823
670
  }
824
671
  });
825
672
  });
826
- // Endpoint to download the matter storage file
827
673
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
828
674
  this.log.debug('The frontend sent /api/download-mjstorage');
829
675
  if (!this.validateReq(req, res))
830
676
  return;
831
677
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
832
678
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
833
- /* istanbul ignore if */
834
679
  if (error) {
835
680
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
836
681
  res.status(500).send('Error downloading the matter storage zip file');
837
682
  }
838
683
  });
839
684
  });
840
- // Endpoint to download the matterbridge plugin directory
841
685
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
842
686
  this.log.debug('The frontend sent /api/download-pluginstorage');
843
687
  if (!this.validateReq(req, res))
844
688
  return;
845
689
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
846
690
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
847
- /* istanbul ignore if */
848
691
  if (error) {
849
692
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
850
693
  res.status(500).send('Error downloading the matterbridge plugin storage file');
851
694
  }
852
695
  });
853
696
  });
854
- // Endpoint to download the matterbridge plugin config files
855
697
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
856
698
  this.log.debug('The frontend sent /api/download-pluginconfig');
857
699
  if (!this.validateReq(req, res))
858
700
  return;
859
701
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
860
702
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
861
- /* istanbul ignore if */
862
703
  if (error) {
863
704
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
864
705
  res.status(500).send('Error downloading the matterbridge plugin config file');
865
706
  }
866
707
  });
867
708
  });
868
- // Endpoint to download the matterbridge backup (created with the backup command)
869
709
  this.expressApp.get('/api/download-backup', async (req, res) => {
870
710
  this.log.debug('The frontend sent /api/download-backup');
871
711
  if (!this.validateReq(req, res))
872
712
  return;
873
713
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
874
- /* istanbul ignore if */
875
714
  if (error) {
876
715
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
877
716
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
878
717
  }
879
718
  });
880
719
  });
881
- // Endpoint to upload a package
882
720
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
883
721
  if (!this.validateReq(req, res))
884
722
  return;
885
723
  const { filename } = req.body;
886
724
  const file = req.file;
887
- /* istanbul ignore if */
888
725
  if (!file || !filename) {
889
726
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
890
727
  res.status(400).send('Invalid request: file and filename are required');
891
728
  return;
892
729
  }
893
730
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
894
- // Define the path where the plugin file will be saved
895
731
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
896
732
  try {
897
- // Move the uploaded file to the specified path
898
733
  const fs = await import('node:fs');
899
734
  await fs.promises.rename(file.path, filePath);
900
735
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
901
- // Install the plugin package
902
736
  if (filename.endsWith('.tgz')) {
903
737
  const { spawnCommand } = await import('./spawn.js');
904
738
  if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
@@ -926,7 +760,6 @@ export class Frontend extends EventEmitter {
926
760
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
927
761
  }
928
762
  });
929
- // Fallback for routing (must be the last route)
930
763
  this.expressApp.use((req, res) => {
931
764
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'apps', 'frontend', 'build');
932
765
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -937,16 +770,13 @@ export class Frontend extends EventEmitter {
937
770
  async stop() {
938
771
  this.log.debug('Stopping the frontend...');
939
772
  const ws = await import('ws');
940
- // Remove listeners from the express app
941
773
  if (this.expressApp) {
942
774
  this.expressApp.removeAllListeners();
943
775
  this.expressApp = undefined;
944
776
  this.log.debug('Frontend app closed successfully');
945
777
  }
946
- // Close the WebSocket server
947
778
  if (this.webSocketServer) {
948
779
  this.log.debug('Closing WebSocket server...');
949
- // Close all active connections
950
780
  this.webSocketServer.clients.forEach((client) => {
951
781
  if (client.readyState === ws.WebSocket.OPEN) {
952
782
  client.close();
@@ -954,9 +784,7 @@ export class Frontend extends EventEmitter {
954
784
  });
955
785
  await withTimeout(new Promise((resolve) => {
956
786
  this.webSocketServer?.close((error) => {
957
- // istanbul ignore if
958
787
  if (error) {
959
- // istanbul ignore next
960
788
  this.log.error(`Error closing WebSocket server: ${error}`);
961
789
  }
962
790
  else {
@@ -969,27 +797,8 @@ export class Frontend extends EventEmitter {
969
797
  this.webSocketServer.removeAllListeners();
970
798
  this.webSocketServer = undefined;
971
799
  }
972
- // Close the http server
973
800
  if (this.httpServer) {
974
801
  this.log.debug('Closing http server...');
975
- /*
976
- await withTimeout(
977
- new Promise<void>((resolve) => {
978
- this.httpServer?.close((error) => {
979
- if (error) {
980
- // istanbul ignore next
981
- this.log.error(`Error closing http server: ${error}`);
982
- } else {
983
- this.log.debug('Http server closed successfully');
984
- this.emit('server_stopped');
985
- }
986
- resolve();
987
- });
988
- }),
989
- 5000,
990
- false,
991
- );
992
- */
993
802
  this.httpServer.close();
994
803
  this.log.debug('Http server closed successfully');
995
804
  this.listening = false;
@@ -998,27 +807,8 @@ export class Frontend extends EventEmitter {
998
807
  this.httpServer = undefined;
999
808
  this.log.debug('Frontend http server closed successfully');
1000
809
  }
1001
- // Close the https server
1002
810
  if (this.httpsServer) {
1003
811
  this.log.debug('Closing https server...');
1004
- /*
1005
- await withTimeout(
1006
- new Promise<void>((resolve) => {
1007
- this.httpsServer?.close((error) => {
1008
- if (error) {
1009
- // istanbul ignore next
1010
- this.log.error(`Error closing https server: ${error}`);
1011
- } else {
1012
- this.log.debug('Https server closed successfully');
1013
- this.emit('server_stopped');
1014
- }
1015
- resolve();
1016
- });
1017
- }),
1018
- 5000,
1019
- false,
1020
- );
1021
- */
1022
812
  this.httpsServer.close();
1023
813
  this.log.debug('Https server closed successfully');
1024
814
  this.listening = false;
@@ -1029,13 +819,7 @@ export class Frontend extends EventEmitter {
1029
819
  }
1030
820
  this.log.debug('Frontend stopped successfully');
1031
821
  }
1032
- /**
1033
- * Retrieves the api settings data.
1034
- *
1035
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
1036
- */
1037
822
  async getApiSettings() {
1038
- // Update the variable system information properties
1039
823
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
1040
824
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
1041
825
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -1045,7 +829,6 @@ export class Frontend extends EventEmitter {
1045
829
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
1046
830
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
1047
831
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
1048
- // Create the matterbridge information
1049
832
  const info = {
1050
833
  homeDirectory: this.matterbridge.homeDirectory,
1051
834
  rootDirectory: this.matterbridge.rootDirectory,
@@ -1081,15 +864,9 @@ export class Frontend extends EventEmitter {
1081
864
  };
1082
865
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
1083
866
  }
1084
- /**
1085
- * Retrieves the reachable attribute.
1086
- *
1087
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
1088
- * @returns {boolean} The reachable attribute.
1089
- */
1090
867
  getReachability(device) {
1091
868
  if (this.matterbridge.hasCleanupStarted)
1092
- return false; // Skip if cleanup has started
869
+ return false;
1093
870
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1094
871
  return false;
1095
872
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -1100,15 +877,9 @@ export class Frontend extends EventEmitter {
1100
877
  return true;
1101
878
  return false;
1102
879
  }
1103
- /**
1104
- * Retrieves the power source attribute.
1105
- *
1106
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1107
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1108
- */
1109
880
  getPowerSource(endpoint) {
1110
881
  if (this.matterbridge.hasCleanupStarted)
1111
- return undefined; // Skip if cleanup has started
882
+ return undefined;
1112
883
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1113
884
  return undefined;
1114
885
  const powerSource = (device) => {
@@ -1123,25 +894,16 @@ export class Frontend extends EventEmitter {
1123
894
  }
1124
895
  return;
1125
896
  };
1126
- // Root endpoint
1127
897
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1128
898
  return powerSource(endpoint);
1129
- // Child endpoints
1130
899
  for (const child of endpoint.getChildEndpoints()) {
1131
- // istanbul ignore else
1132
900
  if (child.hasClusterServer(PowerSource.Cluster.id))
1133
901
  return powerSource(child);
1134
902
  }
1135
903
  }
1136
- /**
1137
- * Retrieves the battery level attribute.
1138
- *
1139
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1140
- * @returns {number | undefined} The battery level attribute.
1141
- */
1142
904
  getBatteryLevel(endpoint) {
1143
905
  if (this.matterbridge.hasCleanupStarted)
1144
- return undefined; // Skip if cleanup has started
906
+ return undefined;
1145
907
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1146
908
  return undefined;
1147
909
  const batteryLevel = (device) => {
@@ -1152,27 +914,16 @@ export class Frontend extends EventEmitter {
1152
914
  }
1153
915
  return undefined;
1154
916
  };
1155
- // Root endpoint
1156
917
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1157
918
  return batteryLevel(endpoint);
1158
- // Child endpoints
1159
919
  for (const child of endpoint.getChildEndpoints()) {
1160
- // istanbul ignore else
1161
920
  if (child.hasClusterServer(PowerSource.Cluster.id))
1162
921
  return batteryLevel(child);
1163
922
  }
1164
923
  }
1165
- /**
1166
- * Retrieves the cluster text description from a given device.
1167
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1168
- *
1169
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1170
- * @returns {string} The attributes description of the cluster servers in the device.
1171
- */
1172
924
  getClusterTextFromDevice(device) {
1173
925
  if (this.matterbridge.hasCleanupStarted)
1174
- return ''; // Skip if cleanup has started
1175
- // istanbul ignore else
926
+ return '';
1176
927
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1177
928
  return '';
1178
929
  const getUserLabel = (device) => {
@@ -1182,7 +933,6 @@ export class Frontend extends EventEmitter {
1182
933
  if (composed)
1183
934
  return 'Composed: ' + composed.value;
1184
935
  }
1185
- // istanbul ignore next cause is not reachable
1186
936
  return '';
1187
937
  };
1188
938
  const getFixedLabel = (device) => {
@@ -1192,13 +942,11 @@ export class Frontend extends EventEmitter {
1192
942
  if (composed)
1193
943
  return 'Composed: ' + composed.value;
1194
944
  }
1195
- // istanbul ignore next cause is not reacheable
1196
945
  return '';
1197
946
  };
1198
947
  let attributes = '';
1199
948
  let supportedModes = [];
1200
949
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1201
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1202
950
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1203
951
  return;
1204
952
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1288,17 +1036,11 @@ export class Frontend extends EventEmitter {
1288
1036
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1289
1037
  attributes += `${getUserLabel(device)} `;
1290
1038
  });
1291
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1292
1039
  return attributes.trimStart().trimEnd();
1293
1040
  }
1294
- /**
1295
- * Retrieves the registered plugins sanitized for res.json().
1296
- *
1297
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1298
- */
1299
1041
  getPlugins() {
1300
1042
  if (this.matterbridge.hasCleanupStarted)
1301
- return []; // Skip if cleanup has started
1043
+ return [];
1302
1044
  const plugins = [];
1303
1045
  for (const plugin of this.matterbridge.plugins.array()) {
1304
1046
  plugins.push({
@@ -1326,27 +1068,18 @@ export class Frontend extends EventEmitter {
1326
1068
  schemaJson: plugin.schemaJson,
1327
1069
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1328
1070
  hasBlackList: plugin.configJson?.blackList !== undefined,
1329
- // Childbridge mode specific data
1330
1071
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1331
1072
  });
1332
1073
  }
1333
1074
  return plugins;
1334
1075
  }
1335
- /**
1336
- * Retrieves the devices from Matterbridge.
1337
- *
1338
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1339
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1340
- */
1341
1076
  getDevices(pluginName) {
1342
1077
  if (this.matterbridge.hasCleanupStarted)
1343
- return []; // Skip if cleanup has started
1078
+ return [];
1344
1079
  const devices = [];
1345
1080
  for (const device of this.matterbridge.devices.array()) {
1346
- // Filter by pluginName if provided
1347
1081
  if (pluginName && pluginName !== device.plugin)
1348
1082
  continue;
1349
- // Check if the device has the required properties
1350
1083
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1351
1084
  continue;
1352
1085
  devices.push({
@@ -1367,40 +1100,24 @@ export class Frontend extends EventEmitter {
1367
1100
  }
1368
1101
  return devices;
1369
1102
  }
1370
- /**
1371
- * Retrieves the clusters from a given plugin and endpoint number.
1372
- *
1373
- * Response for /api/clusters
1374
- *
1375
- * @param {string} pluginName - The name of the plugin.
1376
- * @param {number} endpointNumber - The endpoint number.
1377
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1378
- */
1379
1103
  getClusters(pluginName, endpointNumber) {
1380
1104
  if (this.matterbridge.hasCleanupStarted)
1381
- return; // Skip if cleanup has started
1105
+ return;
1382
1106
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1383
1107
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1384
1108
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1385
1109
  return;
1386
1110
  }
1387
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1388
- // Get the device types from the main endpoint
1389
1111
  const deviceTypes = [];
1390
1112
  const clusters = [];
1391
1113
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1392
1114
  deviceTypes.push(d.deviceType);
1393
1115
  });
1394
- // Get the clusters from the main endpoint
1395
1116
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1396
1117
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1397
1118
  return;
1398
- // istanbul ignore if cause is not reachable without the EveHistory cluster
1399
1119
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1400
1120
  return;
1401
- // console.log(
1402
- // `${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}`,
1403
- // );
1404
1121
  clusters.push({
1405
1122
  endpoint: endpoint.number.toString(),
1406
1123
  number: endpoint.number,
@@ -1414,19 +1131,12 @@ export class Frontend extends EventEmitter {
1414
1131
  attributeLocalValue: attributeValue,
1415
1132
  });
1416
1133
  });
1417
- // Get the child endpoints
1418
1134
  const childEndpoints = endpoint.getChildEndpoints();
1419
- // if (childEndpoints.length === 0) {
1420
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1421
- // }
1422
1135
  childEndpoints.forEach((childEndpoint) => {
1423
- // istanbul ignore if cause is not reachable: should never happen but ...
1424
1136
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1425
1137
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1426
1138
  return;
1427
1139
  }
1428
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1429
- // Get the device types of the child endpoint
1430
1140
  const deviceTypes = [];
1431
1141
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1432
1142
  deviceTypes.push(d.deviceType);
@@ -1434,13 +1144,9 @@ export class Frontend extends EventEmitter {
1434
1144
  childEndpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1435
1145
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1436
1146
  return;
1437
- // istanbul ignore if cause is not reachable without the EveHistory cluster
1438
1147
  if (clusterName === 'EveHistory' &&
1439
1148
  ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1440
1149
  return;
1441
- // console.log(
1442
- // `${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}`,
1443
- // );
1444
1150
  clusters.push({
1445
1151
  endpoint: childEndpoint.number.toString(),
1446
1152
  number: childEndpoint.number,
@@ -1460,7 +1166,6 @@ export class Frontend extends EventEmitter {
1460
1166
  async generateDiagnostic() {
1461
1167
  this.log.debug('Generating diagnostic...');
1462
1168
  const serverNodes = [];
1463
- // istanbul ignore else
1464
1169
  if (this.matterbridge.bridgeMode === 'bridge') {
1465
1170
  if (this.matterbridge.serverNode)
1466
1171
  serverNodes.push(this.matterbridge.serverNode);
@@ -1471,7 +1176,6 @@ export class Frontend extends EventEmitter {
1471
1176
  serverNodes.push(plugin.serverNode);
1472
1177
  }
1473
1178
  }
1474
- // istanbul ignore next
1475
1179
  for (const device of this.matterbridge.devices.array()) {
1476
1180
  if (device.serverNode)
1477
1181
  serverNodes.push(device.serverNode);
@@ -1495,15 +1199,8 @@ export class Frontend extends EventEmitter {
1495
1199
  values: [...serverNodes],
1496
1200
  })));
1497
1201
  delete Logger.destinations.diagnostic;
1498
- await wait(500); // Wait for the log to be written
1202
+ await wait(500);
1499
1203
  }
1500
- /**
1501
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1502
- *
1503
- * @param {WebSocket} client - The websocket client that sent the message.
1504
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1505
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1506
- */
1507
1204
  async wsMessageHandler(client, message) {
1508
1205
  let data;
1509
1206
  const sendResponse = (data) => {
@@ -1521,7 +1218,6 @@ export class Frontend extends EventEmitter {
1521
1218
  client.send(JSON.stringify(data));
1522
1219
  }
1523
1220
  else {
1524
- // istanbul ignore next cause is only a safety check
1525
1221
  this.log.error('Cannot send api response, client not connected');
1526
1222
  }
1527
1223
  };
@@ -1530,7 +1226,7 @@ export class Frontend extends EventEmitter {
1530
1226
  if (!isValidNumber(data.id) ||
1531
1227
  !isValidString(data.dst) ||
1532
1228
  !isValidString(data.src) ||
1533
- !isValidString(data.method) /* || !isValidObject(data.params)*/ ||
1229
+ !isValidString(data.method) ||
1534
1230
  data.dst !== 'Matterbridge') {
1535
1231
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1536
1232
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
@@ -1588,22 +1284,7 @@ export class Frontend extends EventEmitter {
1588
1284
  }
1589
1285
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1590
1286
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1591
- data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
1592
- /*
1593
- const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
1594
- if (plugin) {
1595
- this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1596
- await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
1597
- this.wssSendRestartRequired();
1598
- this.wssSendRefreshRequired('plugins');
1599
- this.wssSendRefreshRequired('devices');
1600
- this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1601
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1602
- } else {
1603
- this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1604
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1605
- }
1606
- */
1287
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1607
1288
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1608
1289
  if (plugin) {
1609
1290
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1616,7 +1297,7 @@ export class Frontend extends EventEmitter {
1616
1297
  this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1617
1298
  return;
1618
1299
  })
1619
- .catch(/* istanbul ignore next */ (_error) => { });
1300
+ .catch((_error) => { });
1620
1301
  }
1621
1302
  else {
1622
1303
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
@@ -1630,10 +1311,6 @@ export class Frontend extends EventEmitter {
1630
1311
  }
1631
1312
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1632
1313
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1633
- /*
1634
- 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);
1635
- await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
1636
- */
1637
1314
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1638
1315
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1639
1316
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1661,11 +1338,9 @@ export class Frontend extends EventEmitter {
1661
1338
  this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
1662
1339
  setImmediate(async () => {
1663
1340
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
1664
- // @ts-expect-error Accessing private method
1665
1341
  if (plugin.serverNode)
1666
1342
  await this.matterbridge.startServerNode(plugin.serverNode);
1667
1343
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1668
- // @ts-expect-error Accessing private method
1669
1344
  await this.matterbridge.startServerNode(device.serverNode);
1670
1345
  this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
1671
1346
  this.wssSendRefreshRequired('plugins');
@@ -1679,16 +1354,12 @@ export class Frontend extends EventEmitter {
1679
1354
  return;
1680
1355
  }
1681
1356
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1682
- // Stop server nodes devices first
1683
1357
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
1684
- // @ts-expect-error Accessing private method
1685
1358
  await this.matterbridge.stopServerNode(device.serverNode);
1686
1359
  device.serverNode = undefined;
1687
1360
  }
1688
- // Then shutdown plugin removing devices, disable it and stop plugin server node
1689
1361
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
1690
1362
  await this.matterbridge.plugins.disable(data.params.pluginName);
1691
- // @ts-expect-error Accessing private method
1692
1363
  if (plugin.serverNode)
1693
1364
  await this.matterbridge.stopServerNode(plugin.serverNode);
1694
1365
  plugin.serverNode = undefined;
@@ -1705,37 +1376,30 @@ export class Frontend extends EventEmitter {
1705
1376
  this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
1706
1377
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1707
1378
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1708
- // Stop server nodes
1709
1379
  if (plugin.serverNode) {
1710
- // @ts-expect-error Accessing private method
1711
1380
  await this.matterbridge.stopServerNode(plugin.serverNode);
1712
1381
  plugin.serverNode = undefined;
1713
1382
  }
1714
1383
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
1715
- // @ts-expect-error Accessing private method
1716
1384
  if (device.serverNode)
1717
1385
  await this.matterbridge.stopServerNode(device.serverNode);
1718
1386
  device.serverNode = undefined;
1719
1387
  this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
1720
1388
  this.matterbridge.devices.remove(device);
1721
1389
  }
1722
- // @ts-expect-error Accessing private method
1723
1390
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1724
1391
  await this.matterbridge.createDynamicPlugin(plugin);
1725
1392
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1726
- plugin.restartRequired = false; // Reset plugin restartRequired
1393
+ plugin.restartRequired = false;
1727
1394
  let needRestart = 0;
1728
1395
  for (const plugin of this.matterbridge.plugins) {
1729
1396
  if (plugin.restartRequired)
1730
1397
  needRestart++;
1731
1398
  }
1732
1399
  if (needRestart === 0)
1733
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1734
- // Start server nodes
1735
- // @ts-expect-error Accessing private method
1400
+ this.wssSendRestartNotRequired(true);
1736
1401
  if (plugin.serverNode)
1737
1402
  await this.matterbridge.startServerNode(plugin.serverNode);
1738
- // @ts-expect-error Accessing private method
1739
1403
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1740
1404
  await this.matterbridge.startServerNode(device.serverNode);
1741
1405
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1767,54 +1431,18 @@ export class Frontend extends EventEmitter {
1767
1431
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1768
1432
  }
1769
1433
  else if (data.method === '/api/shellysysupdate') {
1770
- /*
1771
- const { triggerShellySysUpdate } = await import('./shelly.js');
1772
- triggerShellySysUpdate(this.matterbridge);
1773
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1774
- */
1775
1434
  }
1776
1435
  else if (data.method === '/api/shellymainupdate') {
1777
- /*
1778
- const { triggerShellyMainUpdate } = await import('./shelly.js');
1779
- triggerShellyMainUpdate(this.matterbridge);
1780
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1781
- */
1782
1436
  }
1783
1437
  else if (data.method === '/api/shellycreatesystemlog') {
1784
- /*
1785
- const { createShellySystemLog } = await import('./shelly.js');
1786
- createShellySystemLog(this.matterbridge);
1787
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1788
- */
1789
1438
  }
1790
1439
  else if (data.method === '/api/shellynetconfig') {
1791
- /*
1792
- this.log.debug('/api/shellynetconfig:', data.params);
1793
- const { triggerShellyChangeIp } = await import('./shelly.js');
1794
- triggerShellyChangeIp(this.matterbridge, data.params as { type: 'static' | 'dhcp'; ip: string; subnet: string; gateway: string; dns: string });
1795
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1796
- */
1797
1440
  }
1798
1441
  else if (data.method === '/api/softreset') {
1799
- /*
1800
- const { triggerShellySoftReset } = await import('./shelly.js');
1801
- triggerShellySoftReset(this.matterbridge);
1802
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1803
- */
1804
1442
  }
1805
1443
  else if (data.method === '/api/hardreset') {
1806
- /*
1807
- const { triggerShellyHardReset } = await import('./shelly.js');
1808
- triggerShellyHardReset(this.matterbridge);
1809
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1810
- */
1811
1444
  }
1812
1445
  else if (data.method === '/api/reboot') {
1813
- /*
1814
- const { triggerShellyReboot } = await import('./shelly.js');
1815
- triggerShellyReboot(this.matterbridge);
1816
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1817
- */
1818
1446
  }
1819
1447
  else if (data.method === '/api/restart') {
1820
1448
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
@@ -1954,7 +1582,6 @@ export class Frontend extends EventEmitter {
1954
1582
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
1955
1583
  return;
1956
1584
  }
1957
- // istanbul ignore next
1958
1585
  const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1959
1586
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
1960
1587
  }
@@ -1968,7 +1595,6 @@ export class Frontend extends EventEmitter {
1968
1595
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
1969
1596
  return;
1970
1597
  }
1971
- // istanbul ignore next
1972
1598
  const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1973
1599
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
1974
1600
  }
@@ -2026,22 +1652,22 @@ export class Frontend extends EventEmitter {
2026
1652
  if (isValidString(data.params.value, 4)) {
2027
1653
  this.log.debug('Matterbridge logger level:', data.params.value);
2028
1654
  if (data.params.value === 'Debug') {
2029
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1655
+ await this.matterbridge.setLogLevel("debug");
2030
1656
  }
2031
1657
  else if (data.params.value === 'Info') {
2032
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1658
+ await this.matterbridge.setLogLevel("info");
2033
1659
  }
2034
1660
  else if (data.params.value === 'Notice') {
2035
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1661
+ await this.matterbridge.setLogLevel("notice");
2036
1662
  }
2037
1663
  else if (data.params.value === 'Warn') {
2038
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1664
+ await this.matterbridge.setLogLevel("warn");
2039
1665
  }
2040
1666
  else if (data.params.value === 'Error') {
2041
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1667
+ await this.matterbridge.setLogLevel("error");
2042
1668
  }
2043
1669
  else if (data.params.value === 'Fatal') {
2044
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1670
+ await this.matterbridge.setLogLevel("fatal");
2045
1671
  }
2046
1672
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2047
1673
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -2052,7 +1678,6 @@ export class Frontend extends EventEmitter {
2052
1678
  this.log.debug('Matterbridge file log:', data.params.value);
2053
1679
  this.matterbridge.fileLogger = data.params.value;
2054
1680
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
2055
- // Create the file logger for matterbridge
2056
1681
  if (data.params.value)
2057
1682
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
2058
1683
  else
@@ -2082,12 +1707,11 @@ export class Frontend extends EventEmitter {
2082
1707
  Logger.level = MatterLogLevel.FATAL;
2083
1708
  }
2084
1709
  this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
2085
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
2086
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
2087
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
2088
- callbackLogLevel = "info" /* LogLevel.INFO */;
2089
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
2090
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1710
+ let callbackLogLevel = "notice";
1711
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1712
+ callbackLogLevel = "info";
1713
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1714
+ callbackLogLevel = "debug";
2091
1715
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
2092
1716
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
2093
1717
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -2139,7 +1763,6 @@ export class Frontend extends EventEmitter {
2139
1763
  }
2140
1764
  break;
2141
1765
  case 'setmatterport':
2142
- // eslint-disable-next-line no-case-declarations
2143
1766
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2144
1767
  if (isValidNumber(port, 5540, 5600)) {
2145
1768
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -2159,7 +1782,6 @@ export class Frontend extends EventEmitter {
2159
1782
  }
2160
1783
  break;
2161
1784
  case 'setmatterdiscriminator':
2162
- // eslint-disable-next-line no-case-declarations
2163
1785
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2164
1786
  if (isValidNumber(discriminator, 0, 4095)) {
2165
1787
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -2185,7 +1807,6 @@ export class Frontend extends EventEmitter {
2185
1807
  }
2186
1808
  break;
2187
1809
  case 'setmatterpasscode':
2188
- // eslint-disable-next-line no-case-declarations
2189
1810
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2190
1811
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
2191
1812
  this.matterbridge.passcode = passcode;
@@ -2237,19 +1858,15 @@ export class Frontend extends EventEmitter {
2237
1858
  return;
2238
1859
  }
2239
1860
  const config = plugin.configJson;
2240
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2241
1861
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2242
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2243
1862
  if (select === 'serial')
2244
1863
  this.log.info(`Selected device serial ${data.params.serial}`);
2245
1864
  if (select === 'name')
2246
1865
  this.log.info(`Selected device name ${data.params.name}`);
2247
1866
  if (config && select && (select === 'serial' || select === 'name')) {
2248
- // Remove postfix from the serial if it exists
2249
1867
  if (config.postfix) {
2250
1868
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2251
1869
  }
2252
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2253
1870
  if (isValidArray(config.whiteList, 1)) {
2254
1871
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2255
1872
  config.whiteList.push(data.params.serial);
@@ -2258,7 +1875,6 @@ export class Frontend extends EventEmitter {
2258
1875
  config.whiteList.push(data.params.name);
2259
1876
  }
2260
1877
  }
2261
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2262
1878
  if (isValidArray(config.blackList, 1)) {
2263
1879
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2264
1880
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2289,9 +1905,7 @@ export class Frontend extends EventEmitter {
2289
1905
  return;
2290
1906
  }
2291
1907
  const config = plugin.configJson;
2292
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2293
1908
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2294
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2295
1909
  if (select === 'serial')
2296
1910
  this.log.info(`Unselected device serial ${data.params.serial}`);
2297
1911
  if (select === 'name')
@@ -2300,7 +1914,6 @@ export class Frontend extends EventEmitter {
2300
1914
  if (config.postfix) {
2301
1915
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2302
1916
  }
2303
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2304
1917
  if (isValidArray(config.whiteList, 1)) {
2305
1918
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2306
1919
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2309,7 +1922,6 @@ export class Frontend extends EventEmitter {
2309
1922
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2310
1923
  }
2311
1924
  }
2312
- // Add the serial to the blackList
2313
1925
  if (isValidArray(config.blackList)) {
2314
1926
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2315
1927
  config.blackList.push(data.params.serial);
@@ -2332,7 +1944,6 @@ export class Frontend extends EventEmitter {
2332
1944
  }
2333
1945
  }
2334
1946
  else {
2335
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2336
1947
  const localData = data;
2337
1948
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2338
1949
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2342,46 +1953,23 @@ export class Frontend extends EventEmitter {
2342
1953
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2343
1954
  }
2344
1955
  }
2345
- /**
2346
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2347
- *
2348
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2349
- * @param {string} time - The time string of the message
2350
- * @param {string} name - The logger name of the message
2351
- * @param {string} message - The content of the message.
2352
- *
2353
- * @remarks
2354
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2355
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2356
- * The function sends the message to all connected clients.
2357
- */
2358
1956
  wssSendLogMessage(level, time, name, message) {
2359
1957
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2360
1958
  return;
2361
1959
  if (!level || !time || !name || !message)
2362
1960
  return;
2363
- // Remove ANSI escape codes from the message
2364
- // eslint-disable-next-line no-control-regex
2365
1961
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2366
- // Remove leading asterisks from the message
2367
1962
  message = message.replace(/^\*+/, '');
2368
- // Replace all occurrences of \t and \n
2369
1963
  message = message.replace(/[\t\n]/g, '');
2370
- // Remove non-printable characters
2371
- // eslint-disable-next-line no-control-regex
2372
1964
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2373
- // Replace all occurrences of \" with "
2374
1965
  message = message.replace(/\\"/g, '"');
2375
- // Define the maximum allowed length for continuous characters without a space
2376
1966
  const maxContinuousLength = 100;
2377
1967
  const keepStartLength = 20;
2378
1968
  const keepEndLength = 20;
2379
- // Split the message into words
2380
1969
  if (level !== 'spawn') {
2381
1970
  message = message
2382
1971
  .split(' ')
2383
1972
  .map((word) => {
2384
- // If the word length exceeds the max continuous length, insert spaces and truncate
2385
1973
  if (word.length > maxContinuousLength) {
2386
1974
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2387
1975
  }
@@ -2389,34 +1977,14 @@ export class Frontend extends EventEmitter {
2389
1977
  })
2390
1978
  .join(' ');
2391
1979
  }
2392
- // Send the message to all connected clients
2393
1980
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2394
1981
  }
2395
- /**
2396
- * Sends a need to refresh WebSocket message to all connected clients.
2397
- *
2398
- * @param {string} changed - The changed value.
2399
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2400
- * possible values for changed:
2401
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2402
- * - 'plugins'
2403
- * - 'devices'
2404
- * - 'matter' with param 'matter' (QRDiv component)
2405
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2406
- */
2407
1982
  wssSendRefreshRequired(changed, params) {
2408
1983
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2409
1984
  return;
2410
1985
  this.log.debug('Sending a refresh required message to all connected clients');
2411
- // Send the message to all connected clients
2412
1986
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2413
1987
  }
2414
- /**
2415
- * Sends a need to restart WebSocket message to all connected clients.
2416
- *
2417
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2418
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2419
- */
2420
1988
  wssSendRestartRequired(snackbar = true, fixed = false) {
2421
1989
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2422
1990
  return;
@@ -2425,14 +1993,8 @@ export class Frontend extends EventEmitter {
2425
1993
  this.matterbridge.fixedRestartRequired = fixed;
2426
1994
  if (snackbar === true)
2427
1995
  this.wssSendSnackbarMessage(`Restart required`, 0);
2428
- // Send the message to all connected clients
2429
1996
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2430
1997
  }
2431
- /**
2432
- * Sends a no need to restart WebSocket message to all connected clients.
2433
- *
2434
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2435
- */
2436
1998
  wssSendRestartNotRequired(snackbar = true) {
2437
1999
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2438
2000
  return;
@@ -2440,35 +2002,20 @@ export class Frontend extends EventEmitter {
2440
2002
  this.matterbridge.restartRequired = false;
2441
2003
  if (snackbar === true)
2442
2004
  this.wssSendCloseSnackbarMessage(`Restart required`);
2443
- // Send the message to all connected clients
2444
2005
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2445
2006
  }
2446
- /**
2447
- * Sends a need to update WebSocket message to all connected clients.
2448
- *
2449
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2450
- */
2451
2007
  wssSendUpdateRequired(devVersion = false) {
2452
2008
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2453
2009
  return;
2454
2010
  this.log.debug('Sending an update required message to all connected clients');
2455
2011
  this.matterbridge.updateRequired = true;
2456
- // Send the message to all connected clients
2457
2012
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2458
2013
  }
2459
- /**
2460
- * Sends a cpu update message to all connected clients.
2461
- *
2462
- * @param {number} cpuUsage - The CPU usage percentage to send.
2463
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2464
- */
2465
2014
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2466
2015
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2467
2016
  return;
2468
- // istanbul ignore else
2469
2017
  if (hasParameter('debug'))
2470
2018
  this.log.debug('Sending a cpu update message to all connected clients');
2471
- // Send the message to all connected clients
2472
2019
  this.wssBroadcastMessage({
2473
2020
  id: 0,
2474
2021
  src: 'Matterbridge',
@@ -2478,24 +2025,11 @@ export class Frontend extends EventEmitter {
2478
2025
  response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 },
2479
2026
  });
2480
2027
  }
2481
- /**
2482
- * Sends a memory update message to all connected clients.
2483
- *
2484
- * @param {string} totalMemory - The total memory in bytes.
2485
- * @param {string} freeMemory - The free memory in bytes.
2486
- * @param {string} rss - The resident set size in bytes.
2487
- * @param {string} heapTotal - The total heap memory in bytes.
2488
- * @param {string} heapUsed - The used heap memory in bytes.
2489
- * @param {string} external - The external memory in bytes.
2490
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2491
- */
2492
2028
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2493
2029
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2494
2030
  return;
2495
- // istanbul ignore else
2496
2031
  if (hasParameter('debug'))
2497
2032
  this.log.debug('Sending a memory update message to all connected clients');
2498
- // Send the message to all connected clients
2499
2033
  this.wssBroadcastMessage({
2500
2034
  id: 0,
2501
2035
  src: 'Matterbridge',
@@ -2505,73 +2039,29 @@ export class Frontend extends EventEmitter {
2505
2039
  response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers },
2506
2040
  });
2507
2041
  }
2508
- /**
2509
- * Sends an uptime update message to all connected clients.
2510
- *
2511
- * @param {string} systemUptime - The system uptime in a human-readable format.
2512
- * @param {string} processUptime - The process uptime in a human-readable format.
2513
- */
2514
2042
  wssSendUptimeUpdate(systemUptime, processUptime) {
2515
2043
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2516
2044
  return;
2517
- // istanbul ignore else
2518
2045
  if (hasParameter('debug'))
2519
2046
  this.log.debug('Sending a uptime update message to all connected clients');
2520
- // Send the message to all connected clients
2521
2047
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2522
2048
  }
2523
- /**
2524
- * Sends an open snackbar message to all connected clients.
2525
- *
2526
- * @param {string} message - The message to send.
2527
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2528
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2529
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2530
- *
2531
- * @remarks
2532
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2533
- */
2534
2049
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2535
2050
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2536
2051
  return;
2537
2052
  this.log.debug('Sending a snackbar message to all connected clients');
2538
- // Send the message to all connected clients
2539
2053
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2540
2054
  }
2541
- /**
2542
- * Sends a close snackbar message to all connected clients.
2543
- * It will close the snackbar message with the same message and timeout = 0.
2544
- *
2545
- * @param {string} message - The message to send.
2546
- */
2547
2055
  wssSendCloseSnackbarMessage(message) {
2548
2056
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2549
2057
  return;
2550
2058
  this.log.debug('Sending a close snackbar message to all connected clients');
2551
- // Send the message to all connected clients
2552
2059
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2553
2060
  }
2554
- /**
2555
- * Sends an attribute update message to all connected WebSocket clients.
2556
- *
2557
- * @param {string | undefined} plugin - The name of the plugin.
2558
- * @param {string | undefined} serialNumber - The serial number of the device.
2559
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2560
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2561
- * @param {string} id - The endpoint id where the attribute belongs.
2562
- * @param {string} cluster - The cluster name where the attribute belongs.
2563
- * @param {string} attribute - The name of the attribute that changed.
2564
- * @param {number | string | boolean} value - The new value of the attribute.
2565
- *
2566
- * @remarks
2567
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2568
- * with the updated attribute information.
2569
- */
2570
2061
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2571
2062
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2572
2063
  return;
2573
2064
  this.log.debug('Sending an attribute update message to all connected clients');
2574
- // Send the message to all connected clients
2575
2065
  this.wssBroadcastMessage({
2576
2066
  id: 0,
2577
2067
  src: 'Matterbridge',
@@ -2581,25 +2071,16 @@ export class Frontend extends EventEmitter {
2581
2071
  response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value },
2582
2072
  });
2583
2073
  }
2584
- /**
2585
- * Sends a message to all connected clients.
2586
- * This is an helper function to send a broadcast message to all connected clients.
2587
- *
2588
- * @param {WsMessageBroadcast} msg - The message to send.
2589
- */
2590
2074
  wssBroadcastMessage(msg) {
2591
2075
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2592
2076
  return;
2593
- // Send the message to all connected clients
2594
2077
  const stringifiedMsg = JSON.stringify(msg);
2595
2078
  if (msg.method !== 'log')
2596
2079
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2597
2080
  this.webSocketServer?.clients.forEach((client) => {
2598
- // istanbul ignore else
2599
2081
  if (client.readyState === client.OPEN) {
2600
2082
  client.send(stringifiedMsg);
2601
2083
  }
2602
2084
  });
2603
2085
  }
2604
2086
  }
2605
- //# sourceMappingURL=frontend.js.map