matterbridge 3.5.0 → 3.5.1-dev-20260122-6461be3

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 (330) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README-DOCKER.md +4 -2
  3. package/README.md +4 -1
  4. package/bin/mb_coap.js +1 -1
  5. package/bin/mb_mdns.js +1 -1
  6. package/dist/broadcastServer.d.ts +0 -115
  7. package/dist/broadcastServer.js +1 -119
  8. package/dist/broadcastServerTypes.d.ts +0 -43
  9. package/dist/broadcastServerTypes.js +0 -24
  10. package/dist/cli.d.ts +1 -26
  11. package/dist/cli.js +2 -102
  12. package/dist/cliEmitter.d.ts +0 -36
  13. package/dist/cliEmitter.js +0 -37
  14. package/dist/cliHistory.d.ts +0 -42
  15. package/dist/cliHistory.js +1 -39
  16. package/dist/clusters/export.d.ts +0 -1
  17. package/dist/clusters/export.js +0 -2
  18. package/dist/deviceManager.d.ts +0 -108
  19. package/dist/deviceManager.js +2 -114
  20. package/dist/devices/airConditioner.d.ts +0 -75
  21. package/dist/devices/airConditioner.js +0 -57
  22. package/dist/devices/batteryStorage.d.ts +0 -43
  23. package/dist/devices/batteryStorage.js +1 -48
  24. package/dist/devices/cooktop.d.ts +0 -55
  25. package/dist/devices/cooktop.js +0 -56
  26. package/dist/devices/dishwasher.d.ts +0 -55
  27. package/dist/devices/dishwasher.js +0 -57
  28. package/dist/devices/evse.d.ts +0 -57
  29. package/dist/devices/evse.js +10 -74
  30. package/dist/devices/export.d.ts +0 -1
  31. package/dist/devices/export.js +0 -5
  32. package/dist/devices/extractorHood.d.ts +0 -41
  33. package/dist/devices/extractorHood.js +0 -43
  34. package/dist/devices/heatPump.d.ts +0 -43
  35. package/dist/devices/heatPump.js +2 -50
  36. package/dist/devices/laundryDryer.d.ts +0 -58
  37. package/dist/devices/laundryDryer.js +3 -62
  38. package/dist/devices/laundryWasher.d.ts +0 -64
  39. package/dist/devices/laundryWasher.js +4 -70
  40. package/dist/devices/microwaveOven.d.ts +1 -77
  41. package/dist/devices/microwaveOven.js +5 -88
  42. package/dist/devices/oven.d.ts +0 -82
  43. package/dist/devices/oven.js +0 -85
  44. package/dist/devices/refrigerator.d.ts +0 -100
  45. package/dist/devices/refrigerator.js +0 -102
  46. package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
  47. package/dist/devices/roboticVacuumCleaner.js +9 -100
  48. package/dist/devices/solarPower.d.ts +0 -36
  49. package/dist/devices/solarPower.js +0 -38
  50. package/dist/devices/speaker.d.ts +0 -79
  51. package/dist/devices/speaker.js +0 -84
  52. package/dist/devices/temperatureControl.d.ts +0 -21
  53. package/dist/devices/temperatureControl.js +3 -24
  54. package/dist/devices/waterHeater.d.ts +0 -74
  55. package/dist/devices/waterHeater.js +2 -82
  56. package/dist/frontend.d.ts +4 -187
  57. package/dist/frontend.js +89 -505
  58. package/dist/frontendTypes.d.ts +0 -57
  59. package/dist/frontendTypes.js +0 -45
  60. package/dist/helpers.d.ts +0 -43
  61. package/dist/helpers.js +1 -54
  62. package/dist/index.d.ts +0 -23
  63. package/dist/index.js +0 -25
  64. package/dist/jestutils/export.d.ts +0 -1
  65. package/dist/jestutils/export.js +0 -1
  66. package/dist/jestutils/jestHelpers.d.ts +0 -255
  67. package/dist/jestutils/jestHelpers.js +16 -379
  68. package/dist/logger/export.d.ts +0 -1
  69. package/dist/logger/export.js +0 -1
  70. package/dist/matter/behaviors.d.ts +0 -1
  71. package/dist/matter/behaviors.js +0 -2
  72. package/dist/matter/clusters.d.ts +0 -1
  73. package/dist/matter/clusters.js +0 -2
  74. package/dist/matter/devices.d.ts +0 -1
  75. package/dist/matter/devices.js +0 -2
  76. package/dist/matter/endpoints.d.ts +0 -1
  77. package/dist/matter/endpoints.js +0 -2
  78. package/dist/matter/export.d.ts +0 -1
  79. package/dist/matter/export.js +0 -2
  80. package/dist/matter/types.d.ts +0 -1
  81. package/dist/matter/types.js +0 -2
  82. package/dist/matterNode.d.ts +0 -258
  83. package/dist/matterNode.js +9 -364
  84. package/dist/matterbridge.d.ts +0 -362
  85. package/dist/matterbridge.js +75 -864
  86. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -36
  87. package/dist/matterbridgeAccessoryPlatform.js +0 -38
  88. package/dist/matterbridgeBehaviors.d.ts +0 -24
  89. package/dist/matterbridgeBehaviors.js +5 -68
  90. package/dist/matterbridgeDeviceTypes.d.ts +0 -649
  91. package/dist/matterbridgeDeviceTypes.js +6 -673
  92. package/dist/matterbridgeDynamicPlatform.d.ts +0 -36
  93. package/dist/matterbridgeDynamicPlatform.js +0 -38
  94. package/dist/matterbridgeEndpoint.d.ts +2 -1332
  95. package/dist/matterbridgeEndpoint.js +94 -1459
  96. package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
  97. package/dist/matterbridgeEndpointHelpers.js +22 -487
  98. package/dist/matterbridgeEndpointTypes.d.ts +0 -70
  99. package/dist/matterbridgeEndpointTypes.js +0 -25
  100. package/dist/matterbridgePlatform.d.ts +0 -425
  101. package/dist/matterbridgePlatform.js +2 -453
  102. package/dist/matterbridgeTypes.d.ts +0 -46
  103. package/dist/matterbridgeTypes.js +0 -26
  104. package/dist/mb_coap.d.ts +1 -0
  105. package/dist/{dgram/mb_coap.js → mb_coap.js} +3 -41
  106. package/dist/mb_mdns.d.ts +1 -0
  107. package/dist/{dgram/mb_mdns.js → mb_mdns.js} +47 -81
  108. package/dist/pluginManager.d.ts +0 -305
  109. package/dist/pluginManager.js +8 -345
  110. package/dist/shelly.d.ts +0 -157
  111. package/dist/shelly.js +7 -178
  112. package/dist/spawn.d.ts +1 -0
  113. package/dist/{utils/spawn.js → spawn.js} +3 -73
  114. package/dist/storage/export.d.ts +0 -1
  115. package/dist/storage/export.js +0 -1
  116. package/dist/update.d.ts +0 -75
  117. package/dist/update.js +7 -100
  118. package/dist/utils/export.d.ts +1 -13
  119. package/dist/utils/export.js +1 -13
  120. package/dist/workerGlobalPrefix.d.ts +0 -24
  121. package/dist/workerGlobalPrefix.js +6 -40
  122. package/dist/workerTypes.d.ts +0 -25
  123. package/dist/workerTypes.js +0 -24
  124. package/dist/workers.d.ts +0 -61
  125. package/dist/workers.js +4 -68
  126. package/npm-shrinkwrap.json +80 -50
  127. package/package.json +8 -8
  128. package/dist/broadcastServer.d.ts.map +0 -1
  129. package/dist/broadcastServer.js.map +0 -1
  130. package/dist/broadcastServerTypes.d.ts.map +0 -1
  131. package/dist/broadcastServerTypes.js.map +0 -1
  132. package/dist/cli.d.ts.map +0 -1
  133. package/dist/cli.js.map +0 -1
  134. package/dist/cliEmitter.d.ts.map +0 -1
  135. package/dist/cliEmitter.js.map +0 -1
  136. package/dist/cliHistory.d.ts.map +0 -1
  137. package/dist/cliHistory.js.map +0 -1
  138. package/dist/clusters/export.d.ts.map +0 -1
  139. package/dist/clusters/export.js.map +0 -1
  140. package/dist/deviceManager.d.ts.map +0 -1
  141. package/dist/deviceManager.js.map +0 -1
  142. package/dist/devices/airConditioner.d.ts.map +0 -1
  143. package/dist/devices/airConditioner.js.map +0 -1
  144. package/dist/devices/batteryStorage.d.ts.map +0 -1
  145. package/dist/devices/batteryStorage.js.map +0 -1
  146. package/dist/devices/cooktop.d.ts.map +0 -1
  147. package/dist/devices/cooktop.js.map +0 -1
  148. package/dist/devices/dishwasher.d.ts.map +0 -1
  149. package/dist/devices/dishwasher.js.map +0 -1
  150. package/dist/devices/evse.d.ts.map +0 -1
  151. package/dist/devices/evse.js.map +0 -1
  152. package/dist/devices/export.d.ts.map +0 -1
  153. package/dist/devices/export.js.map +0 -1
  154. package/dist/devices/extractorHood.d.ts.map +0 -1
  155. package/dist/devices/extractorHood.js.map +0 -1
  156. package/dist/devices/heatPump.d.ts.map +0 -1
  157. package/dist/devices/heatPump.js.map +0 -1
  158. package/dist/devices/laundryDryer.d.ts.map +0 -1
  159. package/dist/devices/laundryDryer.js.map +0 -1
  160. package/dist/devices/laundryWasher.d.ts.map +0 -1
  161. package/dist/devices/laundryWasher.js.map +0 -1
  162. package/dist/devices/microwaveOven.d.ts.map +0 -1
  163. package/dist/devices/microwaveOven.js.map +0 -1
  164. package/dist/devices/oven.d.ts.map +0 -1
  165. package/dist/devices/oven.js.map +0 -1
  166. package/dist/devices/refrigerator.d.ts.map +0 -1
  167. package/dist/devices/refrigerator.js.map +0 -1
  168. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  169. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  170. package/dist/devices/solarPower.d.ts.map +0 -1
  171. package/dist/devices/solarPower.js.map +0 -1
  172. package/dist/devices/speaker.d.ts.map +0 -1
  173. package/dist/devices/speaker.js.map +0 -1
  174. package/dist/devices/temperatureControl.d.ts.map +0 -1
  175. package/dist/devices/temperatureControl.js.map +0 -1
  176. package/dist/devices/waterHeater.d.ts.map +0 -1
  177. package/dist/devices/waterHeater.js.map +0 -1
  178. package/dist/dgram/coap.d.ts +0 -205
  179. package/dist/dgram/coap.d.ts.map +0 -1
  180. package/dist/dgram/coap.js +0 -365
  181. package/dist/dgram/coap.js.map +0 -1
  182. package/dist/dgram/dgram.d.ts +0 -144
  183. package/dist/dgram/dgram.d.ts.map +0 -1
  184. package/dist/dgram/dgram.js +0 -363
  185. package/dist/dgram/dgram.js.map +0 -1
  186. package/dist/dgram/mb_coap.d.ts +0 -24
  187. package/dist/dgram/mb_coap.d.ts.map +0 -1
  188. package/dist/dgram/mb_coap.js.map +0 -1
  189. package/dist/dgram/mb_mdns.d.ts +0 -24
  190. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  191. package/dist/dgram/mb_mdns.js.map +0 -1
  192. package/dist/dgram/mdns.d.ts +0 -371
  193. package/dist/dgram/mdns.d.ts.map +0 -1
  194. package/dist/dgram/mdns.js +0 -934
  195. package/dist/dgram/mdns.js.map +0 -1
  196. package/dist/dgram/multicast.d.ts +0 -67
  197. package/dist/dgram/multicast.d.ts.map +0 -1
  198. package/dist/dgram/multicast.js +0 -179
  199. package/dist/dgram/multicast.js.map +0 -1
  200. package/dist/dgram/unicast.d.ts +0 -64
  201. package/dist/dgram/unicast.d.ts.map +0 -1
  202. package/dist/dgram/unicast.js +0 -100
  203. package/dist/dgram/unicast.js.map +0 -1
  204. package/dist/frontend.d.ts.map +0 -1
  205. package/dist/frontend.js.map +0 -1
  206. package/dist/frontendTypes.d.ts.map +0 -1
  207. package/dist/frontendTypes.js.map +0 -1
  208. package/dist/helpers.d.ts.map +0 -1
  209. package/dist/helpers.js.map +0 -1
  210. package/dist/index.d.ts.map +0 -1
  211. package/dist/index.js.map +0 -1
  212. package/dist/jestutils/export.d.ts.map +0 -1
  213. package/dist/jestutils/export.js.map +0 -1
  214. package/dist/jestutils/jestHelpers.d.ts.map +0 -1
  215. package/dist/jestutils/jestHelpers.js.map +0 -1
  216. package/dist/logger/export.d.ts.map +0 -1
  217. package/dist/logger/export.js.map +0 -1
  218. package/dist/matter/behaviors.d.ts.map +0 -1
  219. package/dist/matter/behaviors.js.map +0 -1
  220. package/dist/matter/clusters.d.ts.map +0 -1
  221. package/dist/matter/clusters.js.map +0 -1
  222. package/dist/matter/devices.d.ts.map +0 -1
  223. package/dist/matter/devices.js.map +0 -1
  224. package/dist/matter/endpoints.d.ts.map +0 -1
  225. package/dist/matter/endpoints.js.map +0 -1
  226. package/dist/matter/export.d.ts.map +0 -1
  227. package/dist/matter/export.js.map +0 -1
  228. package/dist/matter/types.d.ts.map +0 -1
  229. package/dist/matter/types.js.map +0 -1
  230. package/dist/matterNode.d.ts.map +0 -1
  231. package/dist/matterNode.js.map +0 -1
  232. package/dist/matterbridge.d.ts.map +0 -1
  233. package/dist/matterbridge.js.map +0 -1
  234. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  235. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  236. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  237. package/dist/matterbridgeBehaviors.js.map +0 -1
  238. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  239. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  240. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  241. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  242. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  243. package/dist/matterbridgeEndpoint.js.map +0 -1
  244. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  245. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  246. package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
  247. package/dist/matterbridgeEndpointTypes.js.map +0 -1
  248. package/dist/matterbridgePlatform.d.ts.map +0 -1
  249. package/dist/matterbridgePlatform.js.map +0 -1
  250. package/dist/matterbridgeTypes.d.ts.map +0 -1
  251. package/dist/matterbridgeTypes.js.map +0 -1
  252. package/dist/pluginManager.d.ts.map +0 -1
  253. package/dist/pluginManager.js.map +0 -1
  254. package/dist/shelly.d.ts.map +0 -1
  255. package/dist/shelly.js.map +0 -1
  256. package/dist/storage/export.d.ts.map +0 -1
  257. package/dist/storage/export.js.map +0 -1
  258. package/dist/update.d.ts.map +0 -1
  259. package/dist/update.js.map +0 -1
  260. package/dist/utils/colorUtils.d.ts +0 -101
  261. package/dist/utils/colorUtils.d.ts.map +0 -1
  262. package/dist/utils/colorUtils.js +0 -282
  263. package/dist/utils/colorUtils.js.map +0 -1
  264. package/dist/utils/commandLine.d.ts +0 -66
  265. package/dist/utils/commandLine.d.ts.map +0 -1
  266. package/dist/utils/commandLine.js +0 -123
  267. package/dist/utils/commandLine.js.map +0 -1
  268. package/dist/utils/copyDirectory.d.ts +0 -35
  269. package/dist/utils/copyDirectory.d.ts.map +0 -1
  270. package/dist/utils/copyDirectory.js +0 -76
  271. package/dist/utils/copyDirectory.js.map +0 -1
  272. package/dist/utils/createDirectory.d.ts +0 -34
  273. package/dist/utils/createDirectory.d.ts.map +0 -1
  274. package/dist/utils/createDirectory.js +0 -54
  275. package/dist/utils/createDirectory.js.map +0 -1
  276. package/dist/utils/createZip.d.ts +0 -39
  277. package/dist/utils/createZip.d.ts.map +0 -1
  278. package/dist/utils/createZip.js +0 -114
  279. package/dist/utils/createZip.js.map +0 -1
  280. package/dist/utils/deepCopy.d.ts +0 -32
  281. package/dist/utils/deepCopy.d.ts.map +0 -1
  282. package/dist/utils/deepCopy.js +0 -79
  283. package/dist/utils/deepCopy.js.map +0 -1
  284. package/dist/utils/deepEqual.d.ts +0 -54
  285. package/dist/utils/deepEqual.d.ts.map +0 -1
  286. package/dist/utils/deepEqual.js +0 -129
  287. package/dist/utils/deepEqual.js.map +0 -1
  288. package/dist/utils/error.d.ts +0 -45
  289. package/dist/utils/error.d.ts.map +0 -1
  290. package/dist/utils/error.js +0 -54
  291. package/dist/utils/error.js.map +0 -1
  292. package/dist/utils/export.d.ts.map +0 -1
  293. package/dist/utils/export.js.map +0 -1
  294. package/dist/utils/format.d.ts +0 -53
  295. package/dist/utils/format.d.ts.map +0 -1
  296. package/dist/utils/format.js +0 -78
  297. package/dist/utils/format.js.map +0 -1
  298. package/dist/utils/hex.d.ts +0 -89
  299. package/dist/utils/hex.d.ts.map +0 -1
  300. package/dist/utils/hex.js +0 -242
  301. package/dist/utils/hex.js.map +0 -1
  302. package/dist/utils/inspector.d.ts +0 -87
  303. package/dist/utils/inspector.d.ts.map +0 -1
  304. package/dist/utils/inspector.js +0 -268
  305. package/dist/utils/inspector.js.map +0 -1
  306. package/dist/utils/isValid.d.ts +0 -103
  307. package/dist/utils/isValid.d.ts.map +0 -1
  308. package/dist/utils/isValid.js +0 -162
  309. package/dist/utils/isValid.js.map +0 -1
  310. package/dist/utils/network.d.ts +0 -141
  311. package/dist/utils/network.d.ts.map +0 -1
  312. package/dist/utils/network.js +0 -314
  313. package/dist/utils/network.js.map +0 -1
  314. package/dist/utils/spawn.d.ts +0 -33
  315. package/dist/utils/spawn.d.ts.map +0 -1
  316. package/dist/utils/spawn.js.map +0 -1
  317. package/dist/utils/tracker.d.ts +0 -108
  318. package/dist/utils/tracker.d.ts.map +0 -1
  319. package/dist/utils/tracker.js +0 -264
  320. package/dist/utils/tracker.js.map +0 -1
  321. package/dist/utils/wait.d.ts +0 -54
  322. package/dist/utils/wait.d.ts.map +0 -1
  323. package/dist/utils/wait.js +0 -125
  324. package/dist/utils/wait.js.map +0 -1
  325. package/dist/workerGlobalPrefix.d.ts.map +0 -1
  326. package/dist/workerGlobalPrefix.js.map +0 -1
  327. package/dist/workerTypes.d.ts.map +0 -1
  328. package/dist/workerTypes.js.map +0 -1
  329. package/dist/workers.d.ts.map +0 -1
  330. package/dist/workers.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,13 +10,8 @@ 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';
13
+ import { createZip, formatBytes, formatPercent, formatUptime, getParameter, hasParameter, inspectError, isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString, wait, withTimeout } from '@matterbridge/utils';
39
14
  import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
40
- import { isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/isValid.js';
41
- import { createZip } from './utils/createZip.js';
42
- import { hasParameter, getParameter } from './utils/commandLine.js';
43
- import { withTimeout, wait } from './utils/wait.js';
44
- import { inspectError } from './utils/error.js';
45
- import { formatBytes, formatUptime, formatPercent } from './utils/format.js';
46
15
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
47
16
  import { cliEmitter, lastOsCpuUsage, lastProcessCpuUsage } from './cliEmitter.js';
48
17
  import { generateHistoryPage } from './cliHistory.js';
@@ -53,6 +22,7 @@ export class Frontend extends EventEmitter {
53
22
  port = 8283;
54
23
  listening = false;
55
24
  storedPassword = undefined;
25
+ authClients = [];
56
26
  expressApp;
57
27
  httpServer;
58
28
  httpsServer;
@@ -63,7 +33,7 @@ export class Frontend extends EventEmitter {
63
33
  constructor(matterbridge) {
64
34
  super();
65
35
  this.matterbridge = matterbridge;
66
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
36
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
67
37
  this.log.logNameColor = '\x1b[38;5;97m';
68
38
  this.server = new BroadcastServer('frontend', this.log);
69
39
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -74,7 +44,6 @@ export class Frontend extends EventEmitter {
74
44
  }
75
45
  async msgHandler(msg) {
76
46
  if (this.server.isWorkerRequest(msg)) {
77
- // istanbul ignore else
78
47
  if (this.verbose)
79
48
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
80
49
  switch (msg.type) {
@@ -126,13 +95,11 @@ export class Frontend extends EventEmitter {
126
95
  this.server.respond({ ...msg, result: { success: true } });
127
96
  break;
128
97
  default:
129
- // istanbul ignore next
130
98
  if (this.verbose)
131
99
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
132
100
  }
133
101
  }
134
102
  if (this.server.isWorkerResponse(msg) && msg.result) {
135
- // istanbul ignore next
136
103
  if (this.verbose)
137
104
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
138
105
  switch (msg.type) {
@@ -164,59 +131,35 @@ export class Frontend extends EventEmitter {
164
131
  set logLevel(logLevel) {
165
132
  this.log.logLevel = logLevel;
166
133
  }
134
+ validateReq(req, res) {
135
+ if (req.ip && !this.authClients.includes(req.ip)) {
136
+ this.log.warn(`Warning blocked unauthorized access request ${req.originalUrl ?? req.url} from ${req.ip}`);
137
+ res.status(401).json({ error: 'Unauthorized' });
138
+ return false;
139
+ }
140
+ return true;
141
+ }
167
142
  async start(port = 8283) {
168
143
  this.port = port;
169
144
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
170
145
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
171
- // Initialize multer with the upload directory
172
146
  const multer = await import('multer');
173
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
147
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
174
148
  const upload = multer.default({ dest: uploadDir });
175
- // Create the express app that serves the frontend
176
149
  const express = await import('express');
177
150
  this.expressApp = express.default();
178
- // Inject logging/debug wrapper for route/middleware registration
179
- /*
180
- const methods = ['get', 'post', 'put', 'delete', 'use'];
181
- for (const method of methods) {
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
- const original = (this.expressApp as any)[method].bind(this.expressApp);
184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
186
- try {
187
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
188
- return original(path, ...rest);
189
- } catch (err) {
190
- console.error(`[ERROR] Failed to register route: ${path}`);
191
- throw err;
192
- }
193
- };
194
- }
195
- */
196
- // Log all requests to the server for debugging
197
- /*
198
- this.expressApp.use((req, res, next) => {
199
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
200
- next();
201
- });
202
- */
203
- // Serve static files from 'frontend/build' directory
204
151
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
205
- // Create a WebSocket server and attach it to the http or https server
206
152
  this.log.debug(`Creating WebSocketServer...`);
207
153
  const ws = await import('ws');
208
154
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
209
155
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
210
156
  this.webSocketServer.on('connection', (ws, request) => {
211
157
  const clientIp = request.socket.remoteAddress;
212
- // Set the global logger callback for the WebSocketServer
213
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
214
- // istanbul ignore else
215
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
216
- callbackLogLevel = "info" /* LogLevel.INFO */;
217
- // istanbul ignore else
218
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
219
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
158
+ let callbackLogLevel = "notice";
159
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
160
+ callbackLogLevel = "info";
161
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
162
+ callbackLogLevel = "debug";
220
163
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
221
164
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
222
165
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -232,33 +175,23 @@ export class Frontend extends EventEmitter {
232
175
  });
233
176
  ws.on('close', () => {
234
177
  this.log.info('WebSocket client disconnected');
235
- // istanbul ignore else
236
178
  if (this.webSocketServer?.clients.size === 0) {
237
179
  AnsiLogger.setGlobalCallback(undefined);
238
180
  this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
181
+ this.authClients = [];
239
182
  }
240
183
  });
241
- // istanbul ignore next
242
184
  ws.on('error', (error) => {
243
- // istanbul ignore next
244
185
  this.log.error(`WebSocket client error: ${error}`);
245
186
  });
246
187
  });
247
188
  this.webSocketServer.on('close', () => {
248
189
  this.log.debug(`WebSocketServer closed`);
249
190
  });
250
- /* With { noServer: true } it never fires
251
- this.webSocketServer.on('listening', () => {
252
- this.log.info(`The WebSocketServer is listening`);
253
- this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
254
- });
255
- */
256
- // istanbul ignore next
257
191
  this.webSocketServer.on('error', (ws, error) => {
258
192
  this.log.error(`WebSocketServer error: ${error}`);
259
193
  });
260
194
  if (!hasParameter('ssl')) {
261
- // Create an HTTP server and attach the express app
262
195
  const http = await import('node:http');
263
196
  try {
264
197
  this.log.debug(`Creating HTTP server...`);
@@ -269,9 +202,7 @@ export class Frontend extends EventEmitter {
269
202
  this.emit('server_error', error);
270
203
  return;
271
204
  }
272
- // Listen on the specified port
273
205
  if (hasParameter('ingress')) {
274
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
275
206
  this.httpServer.listen(this.port, '0.0.0.0', () => {
276
207
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
277
208
  this.listening = true;
@@ -279,17 +210,13 @@ export class Frontend extends EventEmitter {
279
210
  });
280
211
  }
281
212
  else {
282
- // We listen to all available addresses
283
213
  this.httpServer.listen(this.port, getParameter('bind'), () => {
284
214
  const addr = this.httpServer?.address();
285
- // istanbul ignore else
286
215
  if (addr && typeof addr !== 'string') {
287
216
  this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
288
217
  }
289
- // istanbul ignore else
290
218
  if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
291
219
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
292
- // istanbul ignore else
293
220
  if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
294
221
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
295
222
  this.listening = true;
@@ -298,30 +225,24 @@ export class Frontend extends EventEmitter {
298
225
  }
299
226
  this.httpServer.on('upgrade', async (req, socket, head) => {
300
227
  try {
301
- // Only proceed for real WebSocket upgrades
302
- // istanbul ignore next cause is only a safety check
303
228
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
304
229
  this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
305
230
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
306
231
  return socket.destroy();
307
232
  }
308
- // Build a URL so we can read ?password=...
309
233
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
310
- // Validate WebSocket password
311
234
  const password = url.searchParams.get('password') ?? '';
312
235
  if (password !== this.storedPassword) {
313
236
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
314
237
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
315
238
  return socket.destroy();
316
239
  }
317
- // Complete the WebSocket handshake
318
240
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
319
241
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
320
242
  this.webSocketServer?.emit('connection', ws, req);
321
243
  });
322
244
  }
323
245
  catch (err) {
324
- /* istanbul ignore next: only triggered on unexpected internal error */
325
246
  {
326
247
  inspectError(this.log, 'WebSocket upgrade error:', err);
327
248
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -344,7 +265,6 @@ export class Frontend extends EventEmitter {
344
265
  });
345
266
  }
346
267
  else {
347
- // SSL is enabled, load the certificate and the private key
348
268
  let cert;
349
269
  let key;
350
270
  let ca;
@@ -354,7 +274,6 @@ export class Frontend extends EventEmitter {
354
274
  let httpsServerOptions = {};
355
275
  const fs = await import('node:fs');
356
276
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
357
- // Load the p12 certificate and the passphrase
358
277
  try {
359
278
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
360
279
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -366,7 +285,7 @@ export class Frontend extends EventEmitter {
366
285
  }
367
286
  try {
368
287
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
369
- passphrase = passphrase.trim(); // Ensure no extra characters
288
+ passphrase = passphrase.trim();
370
289
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
371
290
  }
372
291
  catch (error) {
@@ -377,7 +296,6 @@ export class Frontend extends EventEmitter {
377
296
  httpsServerOptions = { pfx, passphrase };
378
297
  }
379
298
  else {
380
- // 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.
381
299
  try {
382
300
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
383
301
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -407,10 +325,9 @@ export class Frontend extends EventEmitter {
407
325
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
408
326
  }
409
327
  if (hasParameter('mtls')) {
410
- httpsServerOptions.requestCert = true; // Request client certificate
411
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
328
+ httpsServerOptions.requestCert = true;
329
+ httpsServerOptions.rejectUnauthorized = true;
412
330
  }
413
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
414
331
  const https = await import('node:https');
415
332
  try {
416
333
  this.log.debug(`Creating HTTPS server...`);
@@ -421,9 +338,7 @@ export class Frontend extends EventEmitter {
421
338
  this.emit('server_error', error);
422
339
  return;
423
340
  }
424
- // Listen on the specified port
425
341
  if (hasParameter('ingress')) {
426
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
427
342
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
428
343
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
429
344
  this.listening = true;
@@ -431,17 +346,13 @@ export class Frontend extends EventEmitter {
431
346
  });
432
347
  }
433
348
  else {
434
- // We listen to all available addresses
435
349
  this.httpsServer.listen(this.port, getParameter('bind'), () => {
436
350
  const addr = this.httpsServer?.address();
437
- // istanbul ignore else
438
351
  if (addr && typeof addr !== 'string') {
439
352
  this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
440
353
  }
441
- // istanbul ignore else
442
354
  if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
443
355
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
444
- // istanbul ignore else
445
356
  if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
446
357
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
447
358
  this.listening = true;
@@ -450,29 +361,23 @@ export class Frontend extends EventEmitter {
450
361
  }
451
362
  this.httpsServer.on('upgrade', async (req, socket, head) => {
452
363
  try {
453
- // Only proceed for real WebSocket upgrades
454
- // istanbul ignore next cause is only a safety check
455
364
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
456
365
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
457
366
  return socket.destroy();
458
367
  }
459
- // Build a URL so we can read ?password=...
460
368
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
461
- // Validate WebSocket password
462
369
  const password = url.searchParams.get('password') ?? '';
463
370
  if (password !== this.storedPassword) {
464
371
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
465
372
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
466
373
  return socket.destroy();
467
374
  }
468
- // Complete the WebSocket handshake
469
375
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
470
376
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
471
377
  this.webSocketServer?.emit('connection', ws, req);
472
378
  });
473
379
  }
474
380
  catch (err) {
475
- /* istanbul ignore next: only triggered on unexpected internal error */
476
381
  {
477
382
  inspectError(this.log, 'WebSocket upgrade error:', err);
478
383
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -494,7 +399,6 @@ export class Frontend extends EventEmitter {
494
399
  return;
495
400
  });
496
401
  }
497
- // Subscribe to cli events
498
402
  cliEmitter.removeAllListeners();
499
403
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
500
404
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -505,34 +409,31 @@ export class Frontend extends EventEmitter {
505
409
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
506
410
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
507
411
  });
508
- // Endpoint to validate login code
509
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
510
412
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
511
413
  const { password } = req.body;
512
414
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
513
415
  if (this.storedPassword === '' || password === this.storedPassword) {
514
416
  this.log.debug('/api/login password valid');
515
417
  res.json({ valid: true });
418
+ if (req.ip)
419
+ this.authClients.push(req.ip);
516
420
  }
517
421
  else {
518
422
  this.log.warn('/api/login error wrong password');
519
423
  res.json({ valid: false });
520
424
  }
521
425
  });
522
- // Endpoint to provide health check for docker
523
426
  this.expressApp.get('/health', (req, res) => {
524
427
  this.log.debug('Express received /health');
525
428
  const healthStatus = {
526
- status: 'ok', // Indicate service is healthy
527
- uptime: process.uptime(), // Server uptime in seconds
528
- timestamp: new Date().toISOString(), // Current timestamp
429
+ status: 'ok',
430
+ uptime: process.uptime(),
431
+ timestamp: new Date().toISOString(),
529
432
  };
530
433
  res.status(200).json(healthStatus);
531
434
  });
532
- // Endpoint to provide memory usage details
533
435
  this.expressApp.get('/memory', async (req, res) => {
534
436
  this.log.debug('Express received /memory');
535
- // Memory usage from process
536
437
  const memoryUsageRaw = process.memoryUsage();
537
438
  const memoryUsage = {
538
439
  rss: formatBytes(memoryUsageRaw.rss),
@@ -541,13 +442,10 @@ export class Frontend extends EventEmitter {
541
442
  external: formatBytes(memoryUsageRaw.external),
542
443
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
543
444
  };
544
- // V8 heap statistics
545
445
  const { default: v8 } = await import('node:v8');
546
446
  const heapStatsRaw = v8.getHeapStatistics();
547
447
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
548
- // Format heapStats
549
448
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
550
- // Format heapSpaces
551
449
  const heapSpaces = heapSpacesRaw.map((space) => ({
552
450
  ...space,
553
451
  space_size: formatBytes(space.space_size),
@@ -566,24 +464,28 @@ export class Frontend extends EventEmitter {
566
464
  };
567
465
  res.status(200).json(memoryReport);
568
466
  });
569
- // Endpoint to provide settings
570
467
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
571
468
  this.log.debug('The frontend sent /api/settings');
469
+ if (!this.validateReq(req, res))
470
+ return;
572
471
  res.json(await this.getApiSettings());
573
472
  });
574
- // Endpoint to provide plugins
575
473
  this.expressApp.get('/api/plugins', async (req, res) => {
576
474
  this.log.debug('The frontend sent /api/plugins');
475
+ if (!this.validateReq(req, res))
476
+ return;
577
477
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
578
478
  });
579
- // Endpoint to provide devices
580
479
  this.expressApp.get('/api/devices', async (req, res) => {
581
480
  this.log.debug('The frontend sent /api/devices');
481
+ if (!this.validateReq(req, res))
482
+ return;
582
483
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
583
484
  });
584
- // Endpoint to view the matterbridge log
585
485
  this.expressApp.get('/api/view-mblog', async (req, res) => {
586
486
  this.log.debug('The frontend sent /api/view-mblog');
487
+ if (!this.validateReq(req, res))
488
+ return;
587
489
  try {
588
490
  const fs = await import('node:fs');
589
491
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
@@ -595,9 +497,10 @@ export class Frontend extends EventEmitter {
595
497
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
596
498
  }
597
499
  });
598
- // Endpoint to view the matter.js log
599
500
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
600
501
  this.log.debug('The frontend sent /api/view-mjlog');
502
+ if (!this.validateReq(req, res))
503
+ return;
601
504
  try {
602
505
  const fs = await import('node:fs');
603
506
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
@@ -609,9 +512,10 @@ export class Frontend extends EventEmitter {
609
512
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
610
513
  }
611
514
  });
612
- // Endpoint to view the diagnostic.log
613
515
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
614
516
  this.log.debug('The frontend sent /api/view-diagnostic');
517
+ if (!this.validateReq(req, res))
518
+ return;
615
519
  await this.generateDiagnostic();
616
520
  try {
617
521
  const fs = await import('node:fs');
@@ -620,15 +524,14 @@ export class Frontend extends EventEmitter {
620
524
  res.send(data.slice(29));
621
525
  }
622
526
  catch (error) {
623
- // istanbul ignore next
624
527
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
625
- // istanbul ignore next
626
528
  res.status(500).send('Error reading diagnostic log file.');
627
529
  }
628
530
  });
629
- // Endpoint to download the diagnostic.log
630
531
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
631
532
  this.log.debug(`The frontend sent /api/download-diagnostic`);
533
+ if (!this.validateReq(req, res))
534
+ return;
632
535
  await this.generateDiagnostic();
633
536
  try {
634
537
  const fs = await import('node:fs');
@@ -637,21 +540,20 @@ export class Frontend extends EventEmitter {
637
540
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
638
541
  }
639
542
  catch (error) {
640
- // istanbul ignore next
641
543
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
642
544
  }
643
545
  res.type('text/plain');
644
546
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
645
- /* istanbul ignore if */
646
547
  if (error) {
647
548
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
648
549
  res.status(500).send('Error downloading the diagnostic log file');
649
550
  }
650
551
  });
651
552
  });
652
- // Endpoint to view the history.html
653
553
  this.expressApp.get('/api/viewhistory', async (req, res) => {
654
554
  this.log.debug('The frontend sent /api/viewhistory');
555
+ if (!this.validateReq(req, res))
556
+ return;
655
557
  try {
656
558
  const fs = await import('node:fs');
657
559
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), 'utf8');
@@ -663,9 +565,10 @@ export class Frontend extends EventEmitter {
663
565
  res.status(500).send('Error reading history file.');
664
566
  }
665
567
  });
666
- // Endpoint to download the history.html
667
568
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
668
569
  this.log.debug(`The frontend sent /api/downloadhistory`);
570
+ if (!this.validateReq(req, res))
571
+ return;
669
572
  try {
670
573
  const fs = await import('node:fs');
671
574
  await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), fs.constants.F_OK);
@@ -673,7 +576,6 @@ export class Frontend extends EventEmitter {
673
576
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
674
577
  res.type('text/plain');
675
578
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
676
- /* istanbul ignore if */
677
579
  if (error) {
678
580
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
679
581
  res.status(500).send('Error downloading history file');
@@ -685,9 +587,10 @@ export class Frontend extends EventEmitter {
685
587
  res.status(500).send('Error reading history file.');
686
588
  }
687
589
  });
688
- // Endpoint to view the shelly log
689
590
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
690
591
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
592
+ if (!this.validateReq(req, res))
593
+ return;
691
594
  try {
692
595
  const fs = await import('node:fs');
693
596
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
@@ -699,9 +602,10 @@ export class Frontend extends EventEmitter {
699
602
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
700
603
  }
701
604
  });
702
- // Endpoint to download the matterbridge log
703
605
  this.expressApp.get('/api/download-mblog', async (req, res) => {
704
606
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
607
+ if (!this.validateReq(req, res))
608
+ return;
705
609
  const fs = await import('node:fs');
706
610
  try {
707
611
  await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
@@ -714,16 +618,16 @@ export class Frontend extends EventEmitter {
714
618
  }
715
619
  res.type('text/plain');
716
620
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
717
- /* istanbul ignore if */
718
621
  if (error) {
719
622
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
720
623
  res.status(500).send('Error downloading the matterbridge log file');
721
624
  }
722
625
  });
723
626
  });
724
- // Endpoint to download the matter log
725
627
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
726
628
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
629
+ if (!this.validateReq(req, res))
630
+ return;
727
631
  const fs = await import('node:fs');
728
632
  try {
729
633
  await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
@@ -736,16 +640,16 @@ export class Frontend extends EventEmitter {
736
640
  }
737
641
  res.type('text/plain');
738
642
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
739
- /* istanbul ignore if */
740
643
  if (error) {
741
644
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
742
645
  res.status(500).send('Error downloading the matter log file');
743
646
  }
744
647
  });
745
648
  });
746
- // Endpoint to download the shelly log
747
649
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
748
650
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
651
+ if (!this.validateReq(req, res))
652
+ return;
749
653
  const fs = await import('node:fs');
750
654
  try {
751
655
  await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
@@ -758,93 +662,89 @@ export class Frontend extends EventEmitter {
758
662
  }
759
663
  res.type('text/plain');
760
664
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
761
- /* istanbul ignore if */
762
665
  if (error) {
763
666
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
764
667
  res.status(500).send('Error downloading Shelly system log file');
765
668
  }
766
669
  });
767
670
  });
768
- // Endpoint to download the matterbridge storage directory
769
671
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
770
672
  this.log.debug('The frontend sent /api/download-mbstorage');
673
+ if (!this.validateReq(req, res))
674
+ return;
771
675
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
772
676
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
773
- /* istanbul ignore if */
774
677
  if (error) {
775
678
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
776
679
  res.status(500).send('Error downloading the matterbridge storage file');
777
680
  }
778
681
  });
779
682
  });
780
- // Endpoint to download the matter storage file
781
683
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
782
684
  this.log.debug('The frontend sent /api/download-mjstorage');
685
+ if (!this.validateReq(req, res))
686
+ return;
783
687
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
784
688
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
785
- /* istanbul ignore if */
786
689
  if (error) {
787
690
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
788
691
  res.status(500).send('Error downloading the matter storage zip file');
789
692
  }
790
693
  });
791
694
  });
792
- // Endpoint to download the matterbridge plugin directory
793
695
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
794
696
  this.log.debug('The frontend sent /api/download-pluginstorage');
697
+ if (!this.validateReq(req, res))
698
+ return;
795
699
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
796
700
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
797
- /* istanbul ignore if */
798
701
  if (error) {
799
702
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
800
703
  res.status(500).send('Error downloading the matterbridge plugin storage file');
801
704
  }
802
705
  });
803
706
  });
804
- // Endpoint to download the matterbridge plugin config files
805
707
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
806
708
  this.log.debug('The frontend sent /api/download-pluginconfig');
709
+ if (!this.validateReq(req, res))
710
+ return;
807
711
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
808
712
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
809
- /* istanbul ignore if */
810
713
  if (error) {
811
714
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
812
715
  res.status(500).send('Error downloading the matterbridge plugin config file');
813
716
  }
814
717
  });
815
718
  });
816
- // Endpoint to download the matterbridge backup (created with the backup command)
817
719
  this.expressApp.get('/api/download-backup', async (req, res) => {
818
720
  this.log.debug('The frontend sent /api/download-backup');
721
+ if (!this.validateReq(req, res))
722
+ return;
819
723
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
820
- /* istanbul ignore if */
821
724
  if (error) {
822
725
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
823
726
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
824
727
  }
825
728
  });
826
729
  });
827
- // Endpoint to upload a package
828
730
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
731
+ if (!this.validateReq(req, res))
732
+ return;
829
733
  const { filename } = req.body;
830
734
  const file = req.file;
831
- /* istanbul ignore if */
832
735
  if (!file || !filename) {
833
736
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
834
737
  res.status(400).send('Invalid request: file and filename are required');
835
738
  return;
836
739
  }
837
740
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
838
- // Define the path where the plugin file will be saved
839
741
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
840
742
  try {
841
- // Move the uploaded file to the specified path
842
743
  const fs = await import('node:fs');
843
744
  await fs.promises.rename(file.path, filePath);
844
745
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
845
- // Install the plugin package
846
746
  if (filename.endsWith('.tgz')) {
847
- const { spawnCommand } = await import('./utils/spawn.js');
747
+ const { spawnCommand } = await import('./spawn.js');
848
748
  if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
849
749
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
850
750
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
@@ -870,7 +770,6 @@ export class Frontend extends EventEmitter {
870
770
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
871
771
  }
872
772
  });
873
- // Fallback for routing (must be the last route)
874
773
  this.expressApp.use((req, res) => {
875
774
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
876
775
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -881,16 +780,13 @@ export class Frontend extends EventEmitter {
881
780
  async stop() {
882
781
  this.log.debug('Stopping the frontend...');
883
782
  const ws = await import('ws');
884
- // Remove listeners from the express app
885
783
  if (this.expressApp) {
886
784
  this.expressApp.removeAllListeners();
887
785
  this.expressApp = undefined;
888
786
  this.log.debug('Frontend app closed successfully');
889
787
  }
890
- // Close the WebSocket server
891
788
  if (this.webSocketServer) {
892
789
  this.log.debug('Closing WebSocket server...');
893
- // Close all active connections
894
790
  this.webSocketServer.clients.forEach((client) => {
895
791
  if (client.readyState === ws.WebSocket.OPEN) {
896
792
  client.close();
@@ -898,9 +794,7 @@ export class Frontend extends EventEmitter {
898
794
  });
899
795
  await withTimeout(new Promise((resolve) => {
900
796
  this.webSocketServer?.close((error) => {
901
- // istanbul ignore if
902
797
  if (error) {
903
- // istanbul ignore next
904
798
  this.log.error(`Error closing WebSocket server: ${error}`);
905
799
  }
906
800
  else {
@@ -913,27 +807,8 @@ export class Frontend extends EventEmitter {
913
807
  this.webSocketServer.removeAllListeners();
914
808
  this.webSocketServer = undefined;
915
809
  }
916
- // Close the http server
917
810
  if (this.httpServer) {
918
811
  this.log.debug('Closing http server...');
919
- /*
920
- await withTimeout(
921
- new Promise<void>((resolve) => {
922
- this.httpServer?.close((error) => {
923
- if (error) {
924
- // istanbul ignore next
925
- this.log.error(`Error closing http server: ${error}`);
926
- } else {
927
- this.log.debug('Http server closed successfully');
928
- this.emit('server_stopped');
929
- }
930
- resolve();
931
- });
932
- }),
933
- 5000,
934
- false,
935
- );
936
- */
937
812
  this.httpServer.close();
938
813
  this.log.debug('Http server closed successfully');
939
814
  this.listening = false;
@@ -942,27 +817,8 @@ export class Frontend extends EventEmitter {
942
817
  this.httpServer = undefined;
943
818
  this.log.debug('Frontend http server closed successfully');
944
819
  }
945
- // Close the https server
946
820
  if (this.httpsServer) {
947
821
  this.log.debug('Closing https server...');
948
- /*
949
- await withTimeout(
950
- new Promise<void>((resolve) => {
951
- this.httpsServer?.close((error) => {
952
- if (error) {
953
- // istanbul ignore next
954
- this.log.error(`Error closing https server: ${error}`);
955
- } else {
956
- this.log.debug('Https server closed successfully');
957
- this.emit('server_stopped');
958
- }
959
- resolve();
960
- });
961
- }),
962
- 5000,
963
- false,
964
- );
965
- */
966
822
  this.httpsServer.close();
967
823
  this.log.debug('Https server closed successfully');
968
824
  this.listening = false;
@@ -973,13 +829,7 @@ export class Frontend extends EventEmitter {
973
829
  }
974
830
  this.log.debug('Frontend stopped successfully');
975
831
  }
976
- /**
977
- * Retrieves the api settings data.
978
- *
979
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
980
- */
981
832
  async getApiSettings() {
982
- // Update the variable system information properties
983
833
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
984
834
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
985
835
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -989,7 +839,6 @@ export class Frontend extends EventEmitter {
989
839
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
990
840
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
991
841
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
992
- // Create the matterbridge information
993
842
  const info = {
994
843
  homeDirectory: this.matterbridge.homeDirectory,
995
844
  rootDirectory: this.matterbridge.rootDirectory,
@@ -1025,15 +874,9 @@ export class Frontend extends EventEmitter {
1025
874
  };
1026
875
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
1027
876
  }
1028
- /**
1029
- * Retrieves the reachable attribute.
1030
- *
1031
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
1032
- * @returns {boolean} The reachable attribute.
1033
- */
1034
877
  getReachability(device) {
1035
878
  if (this.matterbridge.hasCleanupStarted)
1036
- return false; // Skip if cleanup has started
879
+ return false;
1037
880
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1038
881
  return false;
1039
882
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -1044,15 +887,9 @@ export class Frontend extends EventEmitter {
1044
887
  return true;
1045
888
  return false;
1046
889
  }
1047
- /**
1048
- * Retrieves the power source attribute.
1049
- *
1050
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1051
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
1052
- */
1053
890
  getPowerSource(endpoint) {
1054
891
  if (this.matterbridge.hasCleanupStarted)
1055
- return undefined; // Skip if cleanup has started
892
+ return undefined;
1056
893
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1057
894
  return undefined;
1058
895
  const powerSource = (device) => {
@@ -1067,25 +904,16 @@ export class Frontend extends EventEmitter {
1067
904
  }
1068
905
  return;
1069
906
  };
1070
- // Root endpoint
1071
907
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1072
908
  return powerSource(endpoint);
1073
- // Child endpoints
1074
909
  for (const child of endpoint.getChildEndpoints()) {
1075
- // istanbul ignore else
1076
910
  if (child.hasClusterServer(PowerSource.Cluster.id))
1077
911
  return powerSource(child);
1078
912
  }
1079
913
  }
1080
- /**
1081
- * Retrieves the battery level attribute.
1082
- *
1083
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
1084
- * @returns {number | undefined} The battery level attribute.
1085
- */
1086
914
  getBatteryLevel(endpoint) {
1087
915
  if (this.matterbridge.hasCleanupStarted)
1088
- return undefined; // Skip if cleanup has started
916
+ return undefined;
1089
917
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1090
918
  return undefined;
1091
919
  const batteryLevel = (device) => {
@@ -1096,27 +924,16 @@ export class Frontend extends EventEmitter {
1096
924
  }
1097
925
  return undefined;
1098
926
  };
1099
- // Root endpoint
1100
927
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1101
928
  return batteryLevel(endpoint);
1102
- // Child endpoints
1103
929
  for (const child of endpoint.getChildEndpoints()) {
1104
- // istanbul ignore else
1105
930
  if (child.hasClusterServer(PowerSource.Cluster.id))
1106
931
  return batteryLevel(child);
1107
932
  }
1108
933
  }
1109
- /**
1110
- * Retrieves the cluster text description from a given device.
1111
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
1112
- *
1113
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
1114
- * @returns {string} The attributes description of the cluster servers in the device.
1115
- */
1116
934
  getClusterTextFromDevice(device) {
1117
935
  if (this.matterbridge.hasCleanupStarted)
1118
- return ''; // Skip if cleanup has started
1119
- // istanbul ignore else
936
+ return '';
1120
937
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1121
938
  return '';
1122
939
  const getUserLabel = (device) => {
@@ -1126,7 +943,6 @@ export class Frontend extends EventEmitter {
1126
943
  if (composed)
1127
944
  return 'Composed: ' + composed.value;
1128
945
  }
1129
- // istanbul ignore next cause is not reachable
1130
946
  return '';
1131
947
  };
1132
948
  const getFixedLabel = (device) => {
@@ -1136,13 +952,11 @@ export class Frontend extends EventEmitter {
1136
952
  if (composed)
1137
953
  return 'Composed: ' + composed.value;
1138
954
  }
1139
- // istanbul ignore next cause is not reacheable
1140
955
  return '';
1141
956
  };
1142
957
  let attributes = '';
1143
958
  let supportedModes = [];
1144
959
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1145
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1146
960
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1147
961
  return;
1148
962
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1232,17 +1046,11 @@ export class Frontend extends EventEmitter {
1232
1046
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1233
1047
  attributes += `${getUserLabel(device)} `;
1234
1048
  });
1235
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1236
1049
  return attributes.trimStart().trimEnd();
1237
1050
  }
1238
- /**
1239
- * Retrieves the registered plugins sanitized for res.json().
1240
- *
1241
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1242
- */
1243
1051
  getPlugins() {
1244
1052
  if (this.matterbridge.hasCleanupStarted)
1245
- return []; // Skip if cleanup has started
1053
+ return [];
1246
1054
  const plugins = [];
1247
1055
  for (const plugin of this.matterbridge.plugins.array()) {
1248
1056
  plugins.push({
@@ -1270,27 +1078,18 @@ export class Frontend extends EventEmitter {
1270
1078
  schemaJson: plugin.schemaJson,
1271
1079
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1272
1080
  hasBlackList: plugin.configJson?.blackList !== undefined,
1273
- // Childbridge mode specific data
1274
1081
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1275
1082
  });
1276
1083
  }
1277
1084
  return plugins;
1278
1085
  }
1279
- /**
1280
- * Retrieves the devices from Matterbridge.
1281
- *
1282
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1283
- * @returns {ApiDevice[]} An array of ApiDevices for the frontend.
1284
- */
1285
1086
  getDevices(pluginName) {
1286
1087
  if (this.matterbridge.hasCleanupStarted)
1287
- return []; // Skip if cleanup has started
1088
+ return [];
1288
1089
  const devices = [];
1289
1090
  for (const device of this.matterbridge.devices.array()) {
1290
- // Filter by pluginName if provided
1291
1091
  if (pluginName && pluginName !== device.plugin)
1292
1092
  continue;
1293
- // Check if the device has the required properties
1294
1093
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1295
1094
  continue;
1296
1095
  devices.push({
@@ -1311,39 +1110,24 @@ export class Frontend extends EventEmitter {
1311
1110
  }
1312
1111
  return devices;
1313
1112
  }
1314
- /**
1315
- * Retrieves the clusters from a given plugin and endpoint number.
1316
- *
1317
- * Response for /api/clusters
1318
- *
1319
- * @param {string} pluginName - The name of the plugin.
1320
- * @param {number} endpointNumber - The endpoint number.
1321
- * @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
1322
- */
1323
1113
  getClusters(pluginName, endpointNumber) {
1324
1114
  if (this.matterbridge.hasCleanupStarted)
1325
- return; // Skip if cleanup has started
1115
+ return;
1326
1116
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1327
1117
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1328
1118
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1329
1119
  return;
1330
1120
  }
1331
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1332
- // Get the device types from the main endpoint
1333
1121
  const deviceTypes = [];
1334
1122
  const clusters = [];
1335
1123
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1336
1124
  deviceTypes.push(d.deviceType);
1337
1125
  });
1338
- // Get the clusters from the main endpoint
1339
1126
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1340
1127
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1341
1128
  return;
1342
1129
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1343
1130
  return;
1344
- // console.log(
1345
- // `${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}`,
1346
- // );
1347
1131
  clusters.push({
1348
1132
  endpoint: endpoint.number.toString(),
1349
1133
  number: endpoint.number,
@@ -1357,19 +1141,12 @@ export class Frontend extends EventEmitter {
1357
1141
  attributeLocalValue: attributeValue,
1358
1142
  });
1359
1143
  });
1360
- // Get the child endpoints
1361
1144
  const childEndpoints = endpoint.getChildEndpoints();
1362
- // if (childEndpoints.length === 0) {
1363
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1364
- // }
1365
1145
  childEndpoints.forEach((childEndpoint) => {
1366
- // istanbul ignore if cause is not reachable: should never happen but ...
1367
1146
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1368
1147
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1369
1148
  return;
1370
1149
  }
1371
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1372
- // Get the device types of the child endpoint
1373
1150
  const deviceTypes = [];
1374
1151
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1375
1152
  deviceTypes.push(d.deviceType);
@@ -1379,9 +1156,6 @@ export class Frontend extends EventEmitter {
1379
1156
  return;
1380
1157
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1381
1158
  return;
1382
- // console.log(
1383
- // `${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}`,
1384
- // );
1385
1159
  clusters.push({
1386
1160
  endpoint: childEndpoint.number.toString(),
1387
1161
  number: childEndpoint.number,
@@ -1401,7 +1175,6 @@ export class Frontend extends EventEmitter {
1401
1175
  async generateDiagnostic() {
1402
1176
  this.log.debug('Generating diagnostic...');
1403
1177
  const serverNodes = [];
1404
- // istanbul ignore else
1405
1178
  if (this.matterbridge.bridgeMode === 'bridge') {
1406
1179
  if (this.matterbridge.serverNode)
1407
1180
  serverNodes.push(this.matterbridge.serverNode);
@@ -1412,7 +1185,6 @@ export class Frontend extends EventEmitter {
1412
1185
  serverNodes.push(plugin.serverNode);
1413
1186
  }
1414
1187
  }
1415
- // istanbul ignore next
1416
1188
  for (const device of this.matterbridge.devices.array()) {
1417
1189
  if (device.serverNode)
1418
1190
  serverNodes.push(device.serverNode);
@@ -1436,15 +1208,8 @@ export class Frontend extends EventEmitter {
1436
1208
  values: [...serverNodes],
1437
1209
  })));
1438
1210
  delete Logger.destinations.diagnostic;
1439
- await wait(500); // Wait for the log to be written
1211
+ await wait(500);
1440
1212
  }
1441
- /**
1442
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1443
- *
1444
- * @param {WebSocket} client - The websocket client that sent the message.
1445
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1446
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1447
- */
1448
1213
  async wsMessageHandler(client, message) {
1449
1214
  let data;
1450
1215
  const sendResponse = (data) => {
@@ -1462,13 +1227,12 @@ export class Frontend extends EventEmitter {
1462
1227
  client.send(JSON.stringify(data));
1463
1228
  }
1464
1229
  else {
1465
- // istanbul ignore next cause is only a safety check
1466
1230
  this.log.error('Cannot send api response, client not connected');
1467
1231
  }
1468
1232
  };
1469
1233
  try {
1470
1234
  data = JSON.parse(message.toString());
1471
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
1235
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1472
1236
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1473
1237
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1474
1238
  return;
@@ -1525,22 +1289,7 @@ export class Frontend extends EventEmitter {
1525
1289
  }
1526
1290
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1527
1291
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1528
- data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
1529
- /*
1530
- const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
1531
- if (plugin) {
1532
- this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1533
- await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
1534
- this.wssSendRestartRequired();
1535
- this.wssSendRefreshRequired('plugins');
1536
- this.wssSendRefreshRequired('devices');
1537
- this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1538
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1539
- } else {
1540
- this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1541
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1542
- }
1543
- */
1292
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1544
1293
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1545
1294
  if (plugin) {
1546
1295
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1553,7 +1302,7 @@ export class Frontend extends EventEmitter {
1553
1302
  this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1554
1303
  return;
1555
1304
  })
1556
- .catch(/* istanbul ignore next */ (_error) => { });
1305
+ .catch((_error) => { });
1557
1306
  }
1558
1307
  else {
1559
1308
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
@@ -1567,10 +1316,6 @@ export class Frontend extends EventEmitter {
1567
1316
  }
1568
1317
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1569
1318
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1570
- /*
1571
- 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);
1572
- await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
1573
- */
1574
1319
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1575
1320
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1576
1321
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1598,10 +1343,8 @@ export class Frontend extends EventEmitter {
1598
1343
  this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
1599
1344
  setImmediate(async () => {
1600
1345
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
1601
- // @ts-expect-error Accessing private method
1602
1346
  if (plugin.serverNode)
1603
1347
  await this.matterbridge.startServerNode(plugin.serverNode);
1604
- // @ts-expect-error Accessing private method
1605
1348
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1606
1349
  await this.matterbridge.startServerNode(device.serverNode);
1607
1350
  this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
@@ -1617,11 +1360,9 @@ export class Frontend extends EventEmitter {
1617
1360
  }
1618
1361
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1619
1362
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
1620
- // @ts-expect-error Accessing private method
1621
1363
  await this.matterbridge.stopServerNode(device.serverNode);
1622
1364
  device.serverNode = undefined;
1623
1365
  }
1624
- // @ts-expect-error Accessing private method
1625
1366
  if (plugin.serverNode)
1626
1367
  await this.matterbridge.stopServerNode(plugin.serverNode);
1627
1368
  plugin.serverNode = undefined;
@@ -1640,37 +1381,30 @@ export class Frontend extends EventEmitter {
1640
1381
  this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
1641
1382
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1642
1383
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1643
- // Stop server nodes
1644
1384
  if (plugin.serverNode) {
1645
- // @ts-expect-error Accessing private method
1646
1385
  await this.matterbridge.stopServerNode(plugin.serverNode);
1647
1386
  plugin.serverNode = undefined;
1648
1387
  }
1649
1388
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
1650
- // @ts-expect-error Accessing private method
1651
1389
  if (device.serverNode)
1652
1390
  await this.matterbridge.stopServerNode(device.serverNode);
1653
1391
  device.serverNode = undefined;
1654
1392
  this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
1655
1393
  this.matterbridge.devices.remove(device);
1656
1394
  }
1657
- // @ts-expect-error Accessing private method
1658
1395
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1659
1396
  await this.matterbridge.createDynamicPlugin(plugin);
1660
1397
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1661
- plugin.restartRequired = false; // Reset plugin restartRequired
1398
+ plugin.restartRequired = false;
1662
1399
  let needRestart = 0;
1663
1400
  for (const plugin of this.matterbridge.plugins) {
1664
1401
  if (plugin.restartRequired)
1665
1402
  needRestart++;
1666
1403
  }
1667
1404
  if (needRestart === 0)
1668
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1669
- // Start server nodes
1670
- // @ts-expect-error Accessing private method
1405
+ this.wssSendRestartNotRequired(true);
1671
1406
  if (plugin.serverNode)
1672
1407
  await this.matterbridge.startServerNode(plugin.serverNode);
1673
- // @ts-expect-error Accessing private method
1674
1408
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1675
1409
  await this.matterbridge.startServerNode(device.serverNode);
1676
1410
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1815,9 +1549,6 @@ export class Frontend extends EventEmitter {
1815
1549
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
1816
1550
  }
1817
1551
  if (data.params.advertise) {
1818
- // TODO: matter.js 0.16.0
1819
- // await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
1820
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1821
1552
  const advertiser = serverNode.env.get(DeviceAdvertiser);
1822
1553
  if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
1823
1554
  await advertiser.advertise(true);
@@ -1881,7 +1612,6 @@ export class Frontend extends EventEmitter {
1881
1612
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
1882
1613
  return;
1883
1614
  }
1884
- // istanbul ignore next
1885
1615
  const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1886
1616
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
1887
1617
  }
@@ -1895,7 +1625,6 @@ export class Frontend extends EventEmitter {
1895
1625
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
1896
1626
  return;
1897
1627
  }
1898
- // istanbul ignore next
1899
1628
  const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1900
1629
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
1901
1630
  }
@@ -1947,22 +1676,22 @@ export class Frontend extends EventEmitter {
1947
1676
  if (isValidString(data.params.value, 4)) {
1948
1677
  this.log.debug('Matterbridge logger level:', data.params.value);
1949
1678
  if (data.params.value === 'Debug') {
1950
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1679
+ await this.matterbridge.setLogLevel("debug");
1951
1680
  }
1952
1681
  else if (data.params.value === 'Info') {
1953
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1682
+ await this.matterbridge.setLogLevel("info");
1954
1683
  }
1955
1684
  else if (data.params.value === 'Notice') {
1956
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1685
+ await this.matterbridge.setLogLevel("notice");
1957
1686
  }
1958
1687
  else if (data.params.value === 'Warn') {
1959
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1688
+ await this.matterbridge.setLogLevel("warn");
1960
1689
  }
1961
1690
  else if (data.params.value === 'Error') {
1962
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1691
+ await this.matterbridge.setLogLevel("error");
1963
1692
  }
1964
1693
  else if (data.params.value === 'Fatal') {
1965
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1694
+ await this.matterbridge.setLogLevel("fatal");
1966
1695
  }
1967
1696
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1968
1697
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1973,7 +1702,6 @@ export class Frontend extends EventEmitter {
1973
1702
  this.log.debug('Matterbridge file log:', data.params.value);
1974
1703
  this.matterbridge.fileLogger = data.params.value;
1975
1704
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1976
- // Create the file logger for matterbridge
1977
1705
  if (data.params.value)
1978
1706
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1979
1707
  else
@@ -2003,12 +1731,11 @@ export class Frontend extends EventEmitter {
2003
1731
  Logger.level = MatterLogLevel.FATAL;
2004
1732
  }
2005
1733
  this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
2006
- // Set the global logger callback for the WebSocketServer to the common minimum logLevel
2007
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
2008
- if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
2009
- callbackLogLevel = "info" /* LogLevel.INFO */;
2010
- if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
2011
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
1734
+ let callbackLogLevel = "notice";
1735
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1736
+ callbackLogLevel = "info";
1737
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1738
+ callbackLogLevel = "debug";
2012
1739
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
2013
1740
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
2014
1741
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -2060,7 +1787,6 @@ export class Frontend extends EventEmitter {
2060
1787
  }
2061
1788
  break;
2062
1789
  case 'setmatterport':
2063
- // eslint-disable-next-line no-case-declarations
2064
1790
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2065
1791
  if (isValidNumber(port, 5540, 5600)) {
2066
1792
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -2080,7 +1806,6 @@ export class Frontend extends EventEmitter {
2080
1806
  }
2081
1807
  break;
2082
1808
  case 'setmatterdiscriminator':
2083
- // eslint-disable-next-line no-case-declarations
2084
1809
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2085
1810
  if (isValidNumber(discriminator, 0, 4095)) {
2086
1811
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -2100,7 +1825,6 @@ export class Frontend extends EventEmitter {
2100
1825
  }
2101
1826
  break;
2102
1827
  case 'setmatterpasscode':
2103
- // eslint-disable-next-line no-case-declarations
2104
1828
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2105
1829
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
2106
1830
  this.matterbridge.passcode = passcode;
@@ -2146,19 +1870,15 @@ export class Frontend extends EventEmitter {
2146
1870
  return;
2147
1871
  }
2148
1872
  const config = plugin.configJson;
2149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2150
1873
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2151
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2152
1874
  if (select === 'serial')
2153
1875
  this.log.info(`Selected device serial ${data.params.serial}`);
2154
1876
  if (select === 'name')
2155
1877
  this.log.info(`Selected device name ${data.params.name}`);
2156
1878
  if (config && select && (select === 'serial' || select === 'name')) {
2157
- // Remove postfix from the serial if it exists
2158
1879
  if (config.postfix) {
2159
1880
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2160
1881
  }
2161
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2162
1882
  if (isValidArray(config.whiteList, 1)) {
2163
1883
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2164
1884
  config.whiteList.push(data.params.serial);
@@ -2167,7 +1887,6 @@ export class Frontend extends EventEmitter {
2167
1887
  config.whiteList.push(data.params.name);
2168
1888
  }
2169
1889
  }
2170
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2171
1890
  if (isValidArray(config.blackList, 1)) {
2172
1891
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2173
1892
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2195,9 +1914,7 @@ export class Frontend extends EventEmitter {
2195
1914
  return;
2196
1915
  }
2197
1916
  const config = plugin.configJson;
2198
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2199
1917
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2200
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2201
1918
  if (select === 'serial')
2202
1919
  this.log.info(`Unselected device serial ${data.params.serial}`);
2203
1920
  if (select === 'name')
@@ -2206,7 +1923,6 @@ export class Frontend extends EventEmitter {
2206
1923
  if (config.postfix) {
2207
1924
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2208
1925
  }
2209
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2210
1926
  if (isValidArray(config.whiteList, 1)) {
2211
1927
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2212
1928
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2215,7 +1931,6 @@ export class Frontend extends EventEmitter {
2215
1931
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2216
1932
  }
2217
1933
  }
2218
- // Add the serial to the blackList
2219
1934
  if (isValidArray(config.blackList)) {
2220
1935
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2221
1936
  config.blackList.push(data.params.serial);
@@ -2238,7 +1953,6 @@ export class Frontend extends EventEmitter {
2238
1953
  }
2239
1954
  }
2240
1955
  else {
2241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2242
1956
  const localData = data;
2243
1957
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2244
1958
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2248,46 +1962,23 @@ export class Frontend extends EventEmitter {
2248
1962
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2249
1963
  }
2250
1964
  }
2251
- /**
2252
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2253
- *
2254
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2255
- * @param {string} time - The time string of the message
2256
- * @param {string} name - The logger name of the message
2257
- * @param {string} message - The content of the message.
2258
- *
2259
- * @remarks
2260
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2261
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2262
- * The function sends the message to all connected clients.
2263
- */
2264
1965
  wssSendLogMessage(level, time, name, message) {
2265
1966
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2266
1967
  return;
2267
1968
  if (!level || !time || !name || !message)
2268
1969
  return;
2269
- // Remove ANSI escape codes from the message
2270
- // eslint-disable-next-line no-control-regex
2271
1970
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2272
- // Remove leading asterisks from the message
2273
1971
  message = message.replace(/^\*+/, '');
2274
- // Replace all occurrences of \t and \n
2275
1972
  message = message.replace(/[\t\n]/g, '');
2276
- // Remove non-printable characters
2277
- // eslint-disable-next-line no-control-regex
2278
1973
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2279
- // Replace all occurrences of \" with "
2280
1974
  message = message.replace(/\\"/g, '"');
2281
- // Define the maximum allowed length for continuous characters without a space
2282
1975
  const maxContinuousLength = 100;
2283
1976
  const keepStartLength = 20;
2284
1977
  const keepEndLength = 20;
2285
- // Split the message into words
2286
1978
  if (level !== 'spawn') {
2287
1979
  message = message
2288
1980
  .split(' ')
2289
1981
  .map((word) => {
2290
- // If the word length exceeds the max continuous length, insert spaces and truncate
2291
1982
  if (word.length > maxContinuousLength) {
2292
1983
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2293
1984
  }
@@ -2295,34 +1986,14 @@ export class Frontend extends EventEmitter {
2295
1986
  })
2296
1987
  .join(' ');
2297
1988
  }
2298
- // Send the message to all connected clients
2299
1989
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2300
1990
  }
2301
- /**
2302
- * Sends a need to refresh WebSocket message to all connected clients.
2303
- *
2304
- * @param {string} changed - The changed value.
2305
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2306
- * possible values for changed:
2307
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2308
- * - 'plugins'
2309
- * - 'devices'
2310
- * - 'matter' with param 'matter' (QRDiv component)
2311
- * @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
2312
- */
2313
1991
  wssSendRefreshRequired(changed, params) {
2314
1992
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2315
1993
  return;
2316
1994
  this.log.debug('Sending a refresh required message to all connected clients');
2317
- // Send the message to all connected clients
2318
1995
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2319
1996
  }
2320
- /**
2321
- * Sends a need to restart WebSocket message to all connected clients.
2322
- *
2323
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2324
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2325
- */
2326
1997
  wssSendRestartRequired(snackbar = true, fixed = false) {
2327
1998
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2328
1999
  return;
@@ -2331,14 +2002,8 @@ export class Frontend extends EventEmitter {
2331
2002
  this.matterbridge.fixedRestartRequired = fixed;
2332
2003
  if (snackbar === true)
2333
2004
  this.wssSendSnackbarMessage(`Restart required`, 0);
2334
- // Send the message to all connected clients
2335
2005
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2336
2006
  }
2337
- /**
2338
- * Sends a no need to restart WebSocket message to all connected clients.
2339
- *
2340
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2341
- */
2342
2007
  wssSendRestartNotRequired(snackbar = true) {
2343
2008
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2344
2009
  return;
@@ -2346,145 +2011,64 @@ export class Frontend extends EventEmitter {
2346
2011
  this.matterbridge.restartRequired = false;
2347
2012
  if (snackbar === true)
2348
2013
  this.wssSendCloseSnackbarMessage(`Restart required`);
2349
- // Send the message to all connected clients
2350
2014
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2351
2015
  }
2352
- /**
2353
- * Sends a need to update WebSocket message to all connected clients.
2354
- *
2355
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2356
- */
2357
2016
  wssSendUpdateRequired(devVersion = false) {
2358
2017
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2359
2018
  return;
2360
2019
  this.log.debug('Sending an update required message to all connected clients');
2361
2020
  this.matterbridge.updateRequired = true;
2362
- // Send the message to all connected clients
2363
2021
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2364
2022
  }
2365
- /**
2366
- * Sends a cpu update message to all connected clients.
2367
- *
2368
- * @param {number} cpuUsage - The CPU usage percentage to send.
2369
- * @param {number} processCpuUsage - The CPU usage percentage of the process to send.
2370
- */
2371
2023
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2372
2024
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2373
2025
  return;
2374
- // istanbul ignore else
2375
2026
  if (hasParameter('debug'))
2376
2027
  this.log.debug('Sending a cpu update message to all connected clients');
2377
- // Send the message to all connected clients
2378
2028
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 } });
2379
2029
  }
2380
- /**
2381
- * Sends a memory update message to all connected clients.
2382
- *
2383
- * @param {string} totalMemory - The total memory in bytes.
2384
- * @param {string} freeMemory - The free memory in bytes.
2385
- * @param {string} rss - The resident set size in bytes.
2386
- * @param {string} heapTotal - The total heap memory in bytes.
2387
- * @param {string} heapUsed - The used heap memory in bytes.
2388
- * @param {string} external - The external memory in bytes.
2389
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2390
- */
2391
2030
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2392
2031
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2393
2032
  return;
2394
- // istanbul ignore else
2395
2033
  if (hasParameter('debug'))
2396
2034
  this.log.debug('Sending a memory update message to all connected clients');
2397
- // Send the message to all connected clients
2398
2035
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2399
2036
  }
2400
- /**
2401
- * Sends an uptime update message to all connected clients.
2402
- *
2403
- * @param {string} systemUptime - The system uptime in a human-readable format.
2404
- * @param {string} processUptime - The process uptime in a human-readable format.
2405
- */
2406
2037
  wssSendUptimeUpdate(systemUptime, processUptime) {
2407
2038
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2408
2039
  return;
2409
- // istanbul ignore else
2410
2040
  if (hasParameter('debug'))
2411
2041
  this.log.debug('Sending a uptime update message to all connected clients');
2412
- // Send the message to all connected clients
2413
2042
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2414
2043
  }
2415
- /**
2416
- * Sends an open snackbar message to all connected clients.
2417
- *
2418
- * @param {string} message - The message to send.
2419
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2420
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2421
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2422
- *
2423
- * @remarks
2424
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2425
- */
2426
2044
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2427
2045
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2428
2046
  return;
2429
2047
  this.log.debug('Sending a snackbar message to all connected clients');
2430
- // Send the message to all connected clients
2431
2048
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2432
2049
  }
2433
- /**
2434
- * Sends a close snackbar message to all connected clients.
2435
- * It will close the snackbar message with the same message and timeout = 0.
2436
- *
2437
- * @param {string} message - The message to send.
2438
- */
2439
2050
  wssSendCloseSnackbarMessage(message) {
2440
2051
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2441
2052
  return;
2442
2053
  this.log.debug('Sending a close snackbar message to all connected clients');
2443
- // Send the message to all connected clients
2444
2054
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2445
2055
  }
2446
- /**
2447
- * Sends an attribute update message to all connected WebSocket clients.
2448
- *
2449
- * @param {string | undefined} plugin - The name of the plugin.
2450
- * @param {string | undefined} serialNumber - The serial number of the device.
2451
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2452
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2453
- * @param {string} id - The endpoint id where the attribute belongs.
2454
- * @param {string} cluster - The cluster name where the attribute belongs.
2455
- * @param {string} attribute - The name of the attribute that changed.
2456
- * @param {number | string | boolean} value - The new value of the attribute.
2457
- *
2458
- * @remarks
2459
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2460
- * with the updated attribute information.
2461
- */
2462
2056
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2463
2057
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2464
2058
  return;
2465
2059
  this.log.debug('Sending an attribute update message to all connected clients');
2466
- // Send the message to all connected clients
2467
2060
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2468
2061
  }
2469
- /**
2470
- * Sends a message to all connected clients.
2471
- * This is an helper function to send a broadcast message to all connected clients.
2472
- *
2473
- * @param {WsMessageBroadcast} msg - The message to send.
2474
- */
2475
2062
  wssBroadcastMessage(msg) {
2476
2063
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2477
2064
  return;
2478
- // Send the message to all connected clients
2479
2065
  const stringifiedMsg = JSON.stringify(msg);
2480
2066
  if (msg.method !== 'log')
2481
2067
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2482
2068
  this.webSocketServer?.clients.forEach((client) => {
2483
- // istanbul ignore else
2484
2069
  if (client.readyState === client.OPEN) {
2485
2070
  client.send(stringifiedMsg);
2486
2071
  }
2487
2072
  });
2488
2073
  }
2489
2074
  }
2490
- //# sourceMappingURL=frontend.js.map