matterbridge 3.5.0 → 3.5.1-dev-20260121-22e98b4

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 (328) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/mb_coap.js +1 -1
  3. package/bin/mb_mdns.js +1 -1
  4. package/dist/broadcastServer.d.ts +0 -115
  5. package/dist/broadcastServer.js +1 -119
  6. package/dist/broadcastServerTypes.d.ts +0 -43
  7. package/dist/broadcastServerTypes.js +0 -24
  8. package/dist/cli.d.ts +1 -26
  9. package/dist/cli.js +2 -102
  10. package/dist/cliEmitter.d.ts +0 -36
  11. package/dist/cliEmitter.js +0 -37
  12. package/dist/cliHistory.d.ts +0 -42
  13. package/dist/cliHistory.js +1 -39
  14. package/dist/clusters/export.d.ts +0 -1
  15. package/dist/clusters/export.js +0 -2
  16. package/dist/deviceManager.d.ts +0 -108
  17. package/dist/deviceManager.js +2 -114
  18. package/dist/devices/airConditioner.d.ts +0 -75
  19. package/dist/devices/airConditioner.js +0 -57
  20. package/dist/devices/batteryStorage.d.ts +0 -43
  21. package/dist/devices/batteryStorage.js +1 -48
  22. package/dist/devices/cooktop.d.ts +0 -55
  23. package/dist/devices/cooktop.js +0 -56
  24. package/dist/devices/dishwasher.d.ts +0 -55
  25. package/dist/devices/dishwasher.js +0 -57
  26. package/dist/devices/evse.d.ts +0 -57
  27. package/dist/devices/evse.js +10 -74
  28. package/dist/devices/export.d.ts +0 -1
  29. package/dist/devices/export.js +0 -5
  30. package/dist/devices/extractorHood.d.ts +0 -41
  31. package/dist/devices/extractorHood.js +0 -43
  32. package/dist/devices/heatPump.d.ts +0 -43
  33. package/dist/devices/heatPump.js +2 -50
  34. package/dist/devices/laundryDryer.d.ts +0 -58
  35. package/dist/devices/laundryDryer.js +3 -62
  36. package/dist/devices/laundryWasher.d.ts +0 -64
  37. package/dist/devices/laundryWasher.js +4 -70
  38. package/dist/devices/microwaveOven.d.ts +1 -77
  39. package/dist/devices/microwaveOven.js +5 -88
  40. package/dist/devices/oven.d.ts +0 -82
  41. package/dist/devices/oven.js +0 -85
  42. package/dist/devices/refrigerator.d.ts +0 -100
  43. package/dist/devices/refrigerator.js +0 -102
  44. package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
  45. package/dist/devices/roboticVacuumCleaner.js +9 -100
  46. package/dist/devices/solarPower.d.ts +0 -36
  47. package/dist/devices/solarPower.js +0 -38
  48. package/dist/devices/speaker.d.ts +0 -79
  49. package/dist/devices/speaker.js +0 -84
  50. package/dist/devices/temperatureControl.d.ts +0 -21
  51. package/dist/devices/temperatureControl.js +3 -24
  52. package/dist/devices/waterHeater.d.ts +0 -74
  53. package/dist/devices/waterHeater.js +2 -82
  54. package/dist/frontend.d.ts +0 -187
  55. package/dist/frontend.js +39 -505
  56. package/dist/frontendTypes.d.ts +0 -57
  57. package/dist/frontendTypes.js +0 -45
  58. package/dist/helpers.d.ts +0 -43
  59. package/dist/helpers.js +1 -54
  60. package/dist/index.d.ts +0 -23
  61. package/dist/index.js +0 -25
  62. package/dist/jestutils/export.d.ts +0 -1
  63. package/dist/jestutils/export.js +0 -1
  64. package/dist/jestutils/jestHelpers.d.ts +0 -255
  65. package/dist/jestutils/jestHelpers.js +14 -372
  66. package/dist/logger/export.d.ts +0 -1
  67. package/dist/logger/export.js +0 -1
  68. package/dist/matter/behaviors.d.ts +0 -1
  69. package/dist/matter/behaviors.js +0 -2
  70. package/dist/matter/clusters.d.ts +0 -1
  71. package/dist/matter/clusters.js +0 -2
  72. package/dist/matter/devices.d.ts +0 -1
  73. package/dist/matter/devices.js +0 -2
  74. package/dist/matter/endpoints.d.ts +0 -1
  75. package/dist/matter/endpoints.js +0 -2
  76. package/dist/matter/export.d.ts +0 -1
  77. package/dist/matter/export.js +0 -2
  78. package/dist/matter/types.d.ts +0 -1
  79. package/dist/matter/types.js +0 -2
  80. package/dist/matterNode.d.ts +0 -258
  81. package/dist/matterNode.js +9 -364
  82. package/dist/matterbridge.d.ts +0 -362
  83. package/dist/matterbridge.js +60 -860
  84. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -36
  85. package/dist/matterbridgeAccessoryPlatform.js +0 -38
  86. package/dist/matterbridgeBehaviors.d.ts +0 -24
  87. package/dist/matterbridgeBehaviors.js +5 -68
  88. package/dist/matterbridgeDeviceTypes.d.ts +0 -649
  89. package/dist/matterbridgeDeviceTypes.js +6 -673
  90. package/dist/matterbridgeDynamicPlatform.d.ts +0 -36
  91. package/dist/matterbridgeDynamicPlatform.js +0 -38
  92. package/dist/matterbridgeEndpoint.d.ts +2 -1332
  93. package/dist/matterbridgeEndpoint.js +94 -1459
  94. package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
  95. package/dist/matterbridgeEndpointHelpers.js +21 -486
  96. package/dist/matterbridgeEndpointTypes.d.ts +0 -70
  97. package/dist/matterbridgeEndpointTypes.js +0 -25
  98. package/dist/matterbridgePlatform.d.ts +0 -425
  99. package/dist/matterbridgePlatform.js +2 -453
  100. package/dist/matterbridgeTypes.d.ts +0 -46
  101. package/dist/matterbridgeTypes.js +0 -26
  102. package/dist/mb_coap.d.ts +1 -0
  103. package/dist/{dgram/mb_coap.js → mb_coap.js} +3 -41
  104. package/dist/mb_mdns.d.ts +1 -0
  105. package/dist/{dgram/mb_mdns.js → mb_mdns.js} +37 -81
  106. package/dist/pluginManager.d.ts +0 -305
  107. package/dist/pluginManager.js +8 -345
  108. package/dist/shelly.d.ts +0 -157
  109. package/dist/shelly.js +7 -178
  110. package/dist/spawn.d.ts +1 -0
  111. package/dist/{utils/spawn.js → spawn.js} +3 -73
  112. package/dist/storage/export.d.ts +0 -1
  113. package/dist/storage/export.js +0 -1
  114. package/dist/update.d.ts +0 -75
  115. package/dist/update.js +7 -100
  116. package/dist/utils/export.d.ts +1 -13
  117. package/dist/utils/export.js +1 -13
  118. package/dist/workerGlobalPrefix.d.ts +0 -24
  119. package/dist/workerGlobalPrefix.js +6 -40
  120. package/dist/workerTypes.d.ts +0 -25
  121. package/dist/workerTypes.js +0 -24
  122. package/dist/workers.d.ts +0 -61
  123. package/dist/workers.js +4 -68
  124. package/npm-shrinkwrap.json +35 -5
  125. package/package.json +5 -5
  126. package/dist/broadcastServer.d.ts.map +0 -1
  127. package/dist/broadcastServer.js.map +0 -1
  128. package/dist/broadcastServerTypes.d.ts.map +0 -1
  129. package/dist/broadcastServerTypes.js.map +0 -1
  130. package/dist/cli.d.ts.map +0 -1
  131. package/dist/cli.js.map +0 -1
  132. package/dist/cliEmitter.d.ts.map +0 -1
  133. package/dist/cliEmitter.js.map +0 -1
  134. package/dist/cliHistory.d.ts.map +0 -1
  135. package/dist/cliHistory.js.map +0 -1
  136. package/dist/clusters/export.d.ts.map +0 -1
  137. package/dist/clusters/export.js.map +0 -1
  138. package/dist/deviceManager.d.ts.map +0 -1
  139. package/dist/deviceManager.js.map +0 -1
  140. package/dist/devices/airConditioner.d.ts.map +0 -1
  141. package/dist/devices/airConditioner.js.map +0 -1
  142. package/dist/devices/batteryStorage.d.ts.map +0 -1
  143. package/dist/devices/batteryStorage.js.map +0 -1
  144. package/dist/devices/cooktop.d.ts.map +0 -1
  145. package/dist/devices/cooktop.js.map +0 -1
  146. package/dist/devices/dishwasher.d.ts.map +0 -1
  147. package/dist/devices/dishwasher.js.map +0 -1
  148. package/dist/devices/evse.d.ts.map +0 -1
  149. package/dist/devices/evse.js.map +0 -1
  150. package/dist/devices/export.d.ts.map +0 -1
  151. package/dist/devices/export.js.map +0 -1
  152. package/dist/devices/extractorHood.d.ts.map +0 -1
  153. package/dist/devices/extractorHood.js.map +0 -1
  154. package/dist/devices/heatPump.d.ts.map +0 -1
  155. package/dist/devices/heatPump.js.map +0 -1
  156. package/dist/devices/laundryDryer.d.ts.map +0 -1
  157. package/dist/devices/laundryDryer.js.map +0 -1
  158. package/dist/devices/laundryWasher.d.ts.map +0 -1
  159. package/dist/devices/laundryWasher.js.map +0 -1
  160. package/dist/devices/microwaveOven.d.ts.map +0 -1
  161. package/dist/devices/microwaveOven.js.map +0 -1
  162. package/dist/devices/oven.d.ts.map +0 -1
  163. package/dist/devices/oven.js.map +0 -1
  164. package/dist/devices/refrigerator.d.ts.map +0 -1
  165. package/dist/devices/refrigerator.js.map +0 -1
  166. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  167. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  168. package/dist/devices/solarPower.d.ts.map +0 -1
  169. package/dist/devices/solarPower.js.map +0 -1
  170. package/dist/devices/speaker.d.ts.map +0 -1
  171. package/dist/devices/speaker.js.map +0 -1
  172. package/dist/devices/temperatureControl.d.ts.map +0 -1
  173. package/dist/devices/temperatureControl.js.map +0 -1
  174. package/dist/devices/waterHeater.d.ts.map +0 -1
  175. package/dist/devices/waterHeater.js.map +0 -1
  176. package/dist/dgram/coap.d.ts +0 -205
  177. package/dist/dgram/coap.d.ts.map +0 -1
  178. package/dist/dgram/coap.js +0 -365
  179. package/dist/dgram/coap.js.map +0 -1
  180. package/dist/dgram/dgram.d.ts +0 -144
  181. package/dist/dgram/dgram.d.ts.map +0 -1
  182. package/dist/dgram/dgram.js +0 -363
  183. package/dist/dgram/dgram.js.map +0 -1
  184. package/dist/dgram/mb_coap.d.ts +0 -24
  185. package/dist/dgram/mb_coap.d.ts.map +0 -1
  186. package/dist/dgram/mb_coap.js.map +0 -1
  187. package/dist/dgram/mb_mdns.d.ts +0 -24
  188. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  189. package/dist/dgram/mb_mdns.js.map +0 -1
  190. package/dist/dgram/mdns.d.ts +0 -371
  191. package/dist/dgram/mdns.d.ts.map +0 -1
  192. package/dist/dgram/mdns.js +0 -934
  193. package/dist/dgram/mdns.js.map +0 -1
  194. package/dist/dgram/multicast.d.ts +0 -67
  195. package/dist/dgram/multicast.d.ts.map +0 -1
  196. package/dist/dgram/multicast.js +0 -179
  197. package/dist/dgram/multicast.js.map +0 -1
  198. package/dist/dgram/unicast.d.ts +0 -64
  199. package/dist/dgram/unicast.d.ts.map +0 -1
  200. package/dist/dgram/unicast.js +0 -100
  201. package/dist/dgram/unicast.js.map +0 -1
  202. package/dist/frontend.d.ts.map +0 -1
  203. package/dist/frontend.js.map +0 -1
  204. package/dist/frontendTypes.d.ts.map +0 -1
  205. package/dist/frontendTypes.js.map +0 -1
  206. package/dist/helpers.d.ts.map +0 -1
  207. package/dist/helpers.js.map +0 -1
  208. package/dist/index.d.ts.map +0 -1
  209. package/dist/index.js.map +0 -1
  210. package/dist/jestutils/export.d.ts.map +0 -1
  211. package/dist/jestutils/export.js.map +0 -1
  212. package/dist/jestutils/jestHelpers.d.ts.map +0 -1
  213. package/dist/jestutils/jestHelpers.js.map +0 -1
  214. package/dist/logger/export.d.ts.map +0 -1
  215. package/dist/logger/export.js.map +0 -1
  216. package/dist/matter/behaviors.d.ts.map +0 -1
  217. package/dist/matter/behaviors.js.map +0 -1
  218. package/dist/matter/clusters.d.ts.map +0 -1
  219. package/dist/matter/clusters.js.map +0 -1
  220. package/dist/matter/devices.d.ts.map +0 -1
  221. package/dist/matter/devices.js.map +0 -1
  222. package/dist/matter/endpoints.d.ts.map +0 -1
  223. package/dist/matter/endpoints.js.map +0 -1
  224. package/dist/matter/export.d.ts.map +0 -1
  225. package/dist/matter/export.js.map +0 -1
  226. package/dist/matter/types.d.ts.map +0 -1
  227. package/dist/matter/types.js.map +0 -1
  228. package/dist/matterNode.d.ts.map +0 -1
  229. package/dist/matterNode.js.map +0 -1
  230. package/dist/matterbridge.d.ts.map +0 -1
  231. package/dist/matterbridge.js.map +0 -1
  232. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  233. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  234. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  235. package/dist/matterbridgeBehaviors.js.map +0 -1
  236. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  237. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  238. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  239. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  240. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  241. package/dist/matterbridgeEndpoint.js.map +0 -1
  242. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  243. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  244. package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
  245. package/dist/matterbridgeEndpointTypes.js.map +0 -1
  246. package/dist/matterbridgePlatform.d.ts.map +0 -1
  247. package/dist/matterbridgePlatform.js.map +0 -1
  248. package/dist/matterbridgeTypes.d.ts.map +0 -1
  249. package/dist/matterbridgeTypes.js.map +0 -1
  250. package/dist/pluginManager.d.ts.map +0 -1
  251. package/dist/pluginManager.js.map +0 -1
  252. package/dist/shelly.d.ts.map +0 -1
  253. package/dist/shelly.js.map +0 -1
  254. package/dist/storage/export.d.ts.map +0 -1
  255. package/dist/storage/export.js.map +0 -1
  256. package/dist/update.d.ts.map +0 -1
  257. package/dist/update.js.map +0 -1
  258. package/dist/utils/colorUtils.d.ts +0 -101
  259. package/dist/utils/colorUtils.d.ts.map +0 -1
  260. package/dist/utils/colorUtils.js +0 -282
  261. package/dist/utils/colorUtils.js.map +0 -1
  262. package/dist/utils/commandLine.d.ts +0 -66
  263. package/dist/utils/commandLine.d.ts.map +0 -1
  264. package/dist/utils/commandLine.js +0 -123
  265. package/dist/utils/commandLine.js.map +0 -1
  266. package/dist/utils/copyDirectory.d.ts +0 -35
  267. package/dist/utils/copyDirectory.d.ts.map +0 -1
  268. package/dist/utils/copyDirectory.js +0 -76
  269. package/dist/utils/copyDirectory.js.map +0 -1
  270. package/dist/utils/createDirectory.d.ts +0 -34
  271. package/dist/utils/createDirectory.d.ts.map +0 -1
  272. package/dist/utils/createDirectory.js +0 -54
  273. package/dist/utils/createDirectory.js.map +0 -1
  274. package/dist/utils/createZip.d.ts +0 -39
  275. package/dist/utils/createZip.d.ts.map +0 -1
  276. package/dist/utils/createZip.js +0 -114
  277. package/dist/utils/createZip.js.map +0 -1
  278. package/dist/utils/deepCopy.d.ts +0 -32
  279. package/dist/utils/deepCopy.d.ts.map +0 -1
  280. package/dist/utils/deepCopy.js +0 -79
  281. package/dist/utils/deepCopy.js.map +0 -1
  282. package/dist/utils/deepEqual.d.ts +0 -54
  283. package/dist/utils/deepEqual.d.ts.map +0 -1
  284. package/dist/utils/deepEqual.js +0 -129
  285. package/dist/utils/deepEqual.js.map +0 -1
  286. package/dist/utils/error.d.ts +0 -45
  287. package/dist/utils/error.d.ts.map +0 -1
  288. package/dist/utils/error.js +0 -54
  289. package/dist/utils/error.js.map +0 -1
  290. package/dist/utils/export.d.ts.map +0 -1
  291. package/dist/utils/export.js.map +0 -1
  292. package/dist/utils/format.d.ts +0 -53
  293. package/dist/utils/format.d.ts.map +0 -1
  294. package/dist/utils/format.js +0 -78
  295. package/dist/utils/format.js.map +0 -1
  296. package/dist/utils/hex.d.ts +0 -89
  297. package/dist/utils/hex.d.ts.map +0 -1
  298. package/dist/utils/hex.js +0 -242
  299. package/dist/utils/hex.js.map +0 -1
  300. package/dist/utils/inspector.d.ts +0 -87
  301. package/dist/utils/inspector.d.ts.map +0 -1
  302. package/dist/utils/inspector.js +0 -268
  303. package/dist/utils/inspector.js.map +0 -1
  304. package/dist/utils/isValid.d.ts +0 -103
  305. package/dist/utils/isValid.d.ts.map +0 -1
  306. package/dist/utils/isValid.js +0 -162
  307. package/dist/utils/isValid.js.map +0 -1
  308. package/dist/utils/network.d.ts +0 -141
  309. package/dist/utils/network.d.ts.map +0 -1
  310. package/dist/utils/network.js +0 -314
  311. package/dist/utils/network.js.map +0 -1
  312. package/dist/utils/spawn.d.ts +0 -33
  313. package/dist/utils/spawn.d.ts.map +0 -1
  314. package/dist/utils/spawn.js.map +0 -1
  315. package/dist/utils/tracker.d.ts +0 -108
  316. package/dist/utils/tracker.d.ts.map +0 -1
  317. package/dist/utils/tracker.js +0 -264
  318. package/dist/utils/tracker.js.map +0 -1
  319. package/dist/utils/wait.d.ts +0 -54
  320. package/dist/utils/wait.d.ts.map +0 -1
  321. package/dist/utils/wait.js +0 -125
  322. package/dist/utils/wait.js.map +0 -1
  323. package/dist/workerGlobalPrefix.d.ts.map +0 -1
  324. package/dist/workerGlobalPrefix.js.map +0 -1
  325. package/dist/workerTypes.d.ts.map +0 -1
  326. package/dist/workerTypes.js.map +0 -1
  327. package/dist/workers.d.ts.map +0 -1
  328. 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';
@@ -63,7 +32,7 @@ export class Frontend extends EventEmitter {
63
32
  constructor(matterbridge) {
64
33
  super();
65
34
  this.matterbridge = matterbridge;
66
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
35
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
67
36
  this.log.logNameColor = '\x1b[38;5;97m';
68
37
  this.server = new BroadcastServer('frontend', this.log);
69
38
  this.server.on('broadcast_message', this.msgHandler.bind(this));
@@ -74,7 +43,6 @@ export class Frontend extends EventEmitter {
74
43
  }
75
44
  async msgHandler(msg) {
76
45
  if (this.server.isWorkerRequest(msg)) {
77
- // istanbul ignore else
78
46
  if (this.verbose)
79
47
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
80
48
  switch (msg.type) {
@@ -126,13 +94,11 @@ export class Frontend extends EventEmitter {
126
94
  this.server.respond({ ...msg, result: { success: true } });
127
95
  break;
128
96
  default:
129
- // istanbul ignore next
130
97
  if (this.verbose)
131
98
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
132
99
  }
133
100
  }
134
101
  if (this.server.isWorkerResponse(msg) && msg.result) {
135
- // istanbul ignore next
136
102
  if (this.verbose)
137
103
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
138
104
  switch (msg.type) {
@@ -168,55 +134,23 @@ export class Frontend extends EventEmitter {
168
134
  this.port = port;
169
135
  this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
170
136
  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
137
  const multer = await import('multer');
173
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
138
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
174
139
  const upload = multer.default({ dest: uploadDir });
175
- // Create the express app that serves the frontend
176
140
  const express = await import('express');
177
141
  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
142
  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
143
  this.log.debug(`Creating WebSocketServer...`);
207
144
  const ws = await import('ws');
208
145
  this.webSocketServer = new ws.WebSocketServer({ noServer: true });
209
146
  this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
210
147
  this.webSocketServer.on('connection', (ws, request) => {
211
148
  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 */;
149
+ let callbackLogLevel = "notice";
150
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
151
+ callbackLogLevel = "info";
152
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
153
+ callbackLogLevel = "debug";
220
154
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
221
155
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
222
156
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -232,33 +166,22 @@ export class Frontend extends EventEmitter {
232
166
  });
233
167
  ws.on('close', () => {
234
168
  this.log.info('WebSocket client disconnected');
235
- // istanbul ignore else
236
169
  if (this.webSocketServer?.clients.size === 0) {
237
170
  AnsiLogger.setGlobalCallback(undefined);
238
171
  this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
239
172
  }
240
173
  });
241
- // istanbul ignore next
242
174
  ws.on('error', (error) => {
243
- // istanbul ignore next
244
175
  this.log.error(`WebSocket client error: ${error}`);
245
176
  });
246
177
  });
247
178
  this.webSocketServer.on('close', () => {
248
179
  this.log.debug(`WebSocketServer closed`);
249
180
  });
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
181
  this.webSocketServer.on('error', (ws, error) => {
258
182
  this.log.error(`WebSocketServer error: ${error}`);
259
183
  });
260
184
  if (!hasParameter('ssl')) {
261
- // Create an HTTP server and attach the express app
262
185
  const http = await import('node:http');
263
186
  try {
264
187
  this.log.debug(`Creating HTTP server...`);
@@ -269,9 +192,7 @@ export class Frontend extends EventEmitter {
269
192
  this.emit('server_error', error);
270
193
  return;
271
194
  }
272
- // Listen on the specified port
273
195
  if (hasParameter('ingress')) {
274
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
275
196
  this.httpServer.listen(this.port, '0.0.0.0', () => {
276
197
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
277
198
  this.listening = true;
@@ -279,17 +200,13 @@ export class Frontend extends EventEmitter {
279
200
  });
280
201
  }
281
202
  else {
282
- // We listen to all available addresses
283
203
  this.httpServer.listen(this.port, getParameter('bind'), () => {
284
204
  const addr = this.httpServer?.address();
285
- // istanbul ignore else
286
205
  if (addr && typeof addr !== 'string') {
287
206
  this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
288
207
  }
289
- // istanbul ignore else
290
208
  if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
291
209
  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
210
  if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
294
211
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
295
212
  this.listening = true;
@@ -298,30 +215,24 @@ export class Frontend extends EventEmitter {
298
215
  }
299
216
  this.httpServer.on('upgrade', async (req, socket, head) => {
300
217
  try {
301
- // Only proceed for real WebSocket upgrades
302
- // istanbul ignore next cause is only a safety check
303
218
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
304
219
  this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
305
220
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
306
221
  return socket.destroy();
307
222
  }
308
- // Build a URL so we can read ?password=...
309
223
  const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
310
- // Validate WebSocket password
311
224
  const password = url.searchParams.get('password') ?? '';
312
225
  if (password !== this.storedPassword) {
313
226
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
314
227
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
315
228
  return socket.destroy();
316
229
  }
317
- // Complete the WebSocket handshake
318
230
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
319
231
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
320
232
  this.webSocketServer?.emit('connection', ws, req);
321
233
  });
322
234
  }
323
235
  catch (err) {
324
- /* istanbul ignore next: only triggered on unexpected internal error */
325
236
  {
326
237
  inspectError(this.log, 'WebSocket upgrade error:', err);
327
238
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -344,7 +255,6 @@ export class Frontend extends EventEmitter {
344
255
  });
345
256
  }
346
257
  else {
347
- // SSL is enabled, load the certificate and the private key
348
258
  let cert;
349
259
  let key;
350
260
  let ca;
@@ -354,7 +264,6 @@ export class Frontend extends EventEmitter {
354
264
  let httpsServerOptions = {};
355
265
  const fs = await import('node:fs');
356
266
  if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
357
- // Load the p12 certificate and the passphrase
358
267
  try {
359
268
  pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
360
269
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
@@ -366,7 +275,7 @@ export class Frontend extends EventEmitter {
366
275
  }
367
276
  try {
368
277
  passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
369
- passphrase = passphrase.trim(); // Ensure no extra characters
278
+ passphrase = passphrase.trim();
370
279
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
371
280
  }
372
281
  catch (error) {
@@ -377,7 +286,6 @@ export class Frontend extends EventEmitter {
377
286
  httpsServerOptions = { pfx, passphrase };
378
287
  }
379
288
  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
289
  try {
382
290
  cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
383
291
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
@@ -407,10 +315,9 @@ export class Frontend extends EventEmitter {
407
315
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
408
316
  }
409
317
  if (hasParameter('mtls')) {
410
- httpsServerOptions.requestCert = true; // Request client certificate
411
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
318
+ httpsServerOptions.requestCert = true;
319
+ httpsServerOptions.rejectUnauthorized = true;
412
320
  }
413
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
414
321
  const https = await import('node:https');
415
322
  try {
416
323
  this.log.debug(`Creating HTTPS server...`);
@@ -421,9 +328,7 @@ export class Frontend extends EventEmitter {
421
328
  this.emit('server_error', error);
422
329
  return;
423
330
  }
424
- // Listen on the specified port
425
331
  if (hasParameter('ingress')) {
426
- // We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
427
332
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
428
333
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
429
334
  this.listening = true;
@@ -431,17 +336,13 @@ export class Frontend extends EventEmitter {
431
336
  });
432
337
  }
433
338
  else {
434
- // We listen to all available addresses
435
339
  this.httpsServer.listen(this.port, getParameter('bind'), () => {
436
340
  const addr = this.httpsServer?.address();
437
- // istanbul ignore else
438
341
  if (addr && typeof addr !== 'string') {
439
342
  this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
440
343
  }
441
- // istanbul ignore else
442
344
  if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
443
345
  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
346
  if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
446
347
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
447
348
  this.listening = true;
@@ -450,29 +351,23 @@ export class Frontend extends EventEmitter {
450
351
  }
451
352
  this.httpsServer.on('upgrade', async (req, socket, head) => {
452
353
  try {
453
- // Only proceed for real WebSocket upgrades
454
- // istanbul ignore next cause is only a safety check
455
354
  if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
456
355
  socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
457
356
  return socket.destroy();
458
357
  }
459
- // Build a URL so we can read ?password=...
460
358
  const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
461
- // Validate WebSocket password
462
359
  const password = url.searchParams.get('password') ?? '';
463
360
  if (password !== this.storedPassword) {
464
361
  this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
465
362
  socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
466
363
  return socket.destroy();
467
364
  }
468
- // Complete the WebSocket handshake
469
365
  this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
470
366
  this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
471
367
  this.webSocketServer?.emit('connection', ws, req);
472
368
  });
473
369
  }
474
370
  catch (err) {
475
- /* istanbul ignore next: only triggered on unexpected internal error */
476
371
  {
477
372
  inspectError(this.log, 'WebSocket upgrade error:', err);
478
373
  socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
@@ -494,7 +389,6 @@ export class Frontend extends EventEmitter {
494
389
  return;
495
390
  });
496
391
  }
497
- // Subscribe to cli events
498
392
  cliEmitter.removeAllListeners();
499
393
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
500
394
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -505,8 +399,6 @@ export class Frontend extends EventEmitter {
505
399
  cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
506
400
  this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
507
401
  });
508
- // Endpoint to validate login code
509
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
510
402
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
511
403
  const { password } = req.body;
512
404
  this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
@@ -519,20 +411,17 @@ export class Frontend extends EventEmitter {
519
411
  res.json({ valid: false });
520
412
  }
521
413
  });
522
- // Endpoint to provide health check for docker
523
414
  this.expressApp.get('/health', (req, res) => {
524
415
  this.log.debug('Express received /health');
525
416
  const healthStatus = {
526
- status: 'ok', // Indicate service is healthy
527
- uptime: process.uptime(), // Server uptime in seconds
528
- timestamp: new Date().toISOString(), // Current timestamp
417
+ status: 'ok',
418
+ uptime: process.uptime(),
419
+ timestamp: new Date().toISOString(),
529
420
  };
530
421
  res.status(200).json(healthStatus);
531
422
  });
532
- // Endpoint to provide memory usage details
533
423
  this.expressApp.get('/memory', async (req, res) => {
534
424
  this.log.debug('Express received /memory');
535
- // Memory usage from process
536
425
  const memoryUsageRaw = process.memoryUsage();
537
426
  const memoryUsage = {
538
427
  rss: formatBytes(memoryUsageRaw.rss),
@@ -541,13 +430,10 @@ export class Frontend extends EventEmitter {
541
430
  external: formatBytes(memoryUsageRaw.external),
542
431
  arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
543
432
  };
544
- // V8 heap statistics
545
433
  const { default: v8 } = await import('node:v8');
546
434
  const heapStatsRaw = v8.getHeapStatistics();
547
435
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
548
- // Format heapStats
549
436
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
550
- // Format heapSpaces
551
437
  const heapSpaces = heapSpacesRaw.map((space) => ({
552
438
  ...space,
553
439
  space_size: formatBytes(space.space_size),
@@ -566,22 +452,18 @@ export class Frontend extends EventEmitter {
566
452
  };
567
453
  res.status(200).json(memoryReport);
568
454
  });
569
- // Endpoint to provide settings
570
455
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
571
456
  this.log.debug('The frontend sent /api/settings');
572
457
  res.json(await this.getApiSettings());
573
458
  });
574
- // Endpoint to provide plugins
575
459
  this.expressApp.get('/api/plugins', async (req, res) => {
576
460
  this.log.debug('The frontend sent /api/plugins');
577
461
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
578
462
  });
579
- // Endpoint to provide devices
580
463
  this.expressApp.get('/api/devices', async (req, res) => {
581
464
  this.log.debug('The frontend sent /api/devices');
582
465
  res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
583
466
  });
584
- // Endpoint to view the matterbridge log
585
467
  this.expressApp.get('/api/view-mblog', async (req, res) => {
586
468
  this.log.debug('The frontend sent /api/view-mblog');
587
469
  try {
@@ -595,7 +477,6 @@ export class Frontend extends EventEmitter {
595
477
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
596
478
  }
597
479
  });
598
- // Endpoint to view the matter.js log
599
480
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
600
481
  this.log.debug('The frontend sent /api/view-mjlog');
601
482
  try {
@@ -609,7 +490,6 @@ export class Frontend extends EventEmitter {
609
490
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
610
491
  }
611
492
  });
612
- // Endpoint to view the diagnostic.log
613
493
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
614
494
  this.log.debug('The frontend sent /api/view-diagnostic');
615
495
  await this.generateDiagnostic();
@@ -620,13 +500,10 @@ export class Frontend extends EventEmitter {
620
500
  res.send(data.slice(29));
621
501
  }
622
502
  catch (error) {
623
- // istanbul ignore next
624
503
  this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
625
- // istanbul ignore next
626
504
  res.status(500).send('Error reading diagnostic log file.');
627
505
  }
628
506
  });
629
- // Endpoint to download the diagnostic.log
630
507
  this.expressApp.get('/api/download-diagnostic', async (req, res) => {
631
508
  this.log.debug(`The frontend sent /api/download-diagnostic`);
632
509
  await this.generateDiagnostic();
@@ -637,19 +514,16 @@ export class Frontend extends EventEmitter {
637
514
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
638
515
  }
639
516
  catch (error) {
640
- // istanbul ignore next
641
517
  this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
642
518
  }
643
519
  res.type('text/plain');
644
520
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
645
- /* istanbul ignore if */
646
521
  if (error) {
647
522
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
648
523
  res.status(500).send('Error downloading the diagnostic log file');
649
524
  }
650
525
  });
651
526
  });
652
- // Endpoint to view the history.html
653
527
  this.expressApp.get('/api/viewhistory', async (req, res) => {
654
528
  this.log.debug('The frontend sent /api/viewhistory');
655
529
  try {
@@ -663,7 +537,6 @@ export class Frontend extends EventEmitter {
663
537
  res.status(500).send('Error reading history file.');
664
538
  }
665
539
  });
666
- // Endpoint to download the history.html
667
540
  this.expressApp.get('/api/downloadhistory', async (req, res) => {
668
541
  this.log.debug(`The frontend sent /api/downloadhistory`);
669
542
  try {
@@ -673,7 +546,6 @@ export class Frontend extends EventEmitter {
673
546
  await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
674
547
  res.type('text/plain');
675
548
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
676
- /* istanbul ignore if */
677
549
  if (error) {
678
550
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
679
551
  res.status(500).send('Error downloading history file');
@@ -685,7 +557,6 @@ export class Frontend extends EventEmitter {
685
557
  res.status(500).send('Error reading history file.');
686
558
  }
687
559
  });
688
- // Endpoint to view the shelly log
689
560
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
690
561
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
691
562
  try {
@@ -699,7 +570,6 @@ export class Frontend extends EventEmitter {
699
570
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
700
571
  }
701
572
  });
702
- // Endpoint to download the matterbridge log
703
573
  this.expressApp.get('/api/download-mblog', async (req, res) => {
704
574
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
705
575
  const fs = await import('node:fs');
@@ -714,14 +584,12 @@ export class Frontend extends EventEmitter {
714
584
  }
715
585
  res.type('text/plain');
716
586
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
717
- /* istanbul ignore if */
718
587
  if (error) {
719
588
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
720
589
  res.status(500).send('Error downloading the matterbridge log file');
721
590
  }
722
591
  });
723
592
  });
724
- // Endpoint to download the matter log
725
593
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
726
594
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
727
595
  const fs = await import('node:fs');
@@ -736,14 +604,12 @@ export class Frontend extends EventEmitter {
736
604
  }
737
605
  res.type('text/plain');
738
606
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
739
- /* istanbul ignore if */
740
607
  if (error) {
741
608
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
742
609
  res.status(500).send('Error downloading the matter log file');
743
610
  }
744
611
  });
745
612
  });
746
- // Endpoint to download the shelly log
747
613
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
748
614
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
749
615
  const fs = await import('node:fs');
@@ -758,93 +624,77 @@ export class Frontend extends EventEmitter {
758
624
  }
759
625
  res.type('text/plain');
760
626
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
761
- /* istanbul ignore if */
762
627
  if (error) {
763
628
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
764
629
  res.status(500).send('Error downloading Shelly system log file');
765
630
  }
766
631
  });
767
632
  });
768
- // Endpoint to download the matterbridge storage directory
769
633
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
770
634
  this.log.debug('The frontend sent /api/download-mbstorage');
771
635
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
772
636
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
773
- /* istanbul ignore if */
774
637
  if (error) {
775
638
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
776
639
  res.status(500).send('Error downloading the matterbridge storage file');
777
640
  }
778
641
  });
779
642
  });
780
- // Endpoint to download the matter storage file
781
643
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
782
644
  this.log.debug('The frontend sent /api/download-mjstorage');
783
645
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
784
646
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
785
- /* istanbul ignore if */
786
647
  if (error) {
787
648
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
788
649
  res.status(500).send('Error downloading the matter storage zip file');
789
650
  }
790
651
  });
791
652
  });
792
- // Endpoint to download the matterbridge plugin directory
793
653
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
794
654
  this.log.debug('The frontend sent /api/download-pluginstorage');
795
655
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
796
656
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
797
- /* istanbul ignore if */
798
657
  if (error) {
799
658
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
800
659
  res.status(500).send('Error downloading the matterbridge plugin storage file');
801
660
  }
802
661
  });
803
662
  });
804
- // Endpoint to download the matterbridge plugin config files
805
663
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
806
664
  this.log.debug('The frontend sent /api/download-pluginconfig');
807
665
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
808
666
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
809
- /* istanbul ignore if */
810
667
  if (error) {
811
668
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
812
669
  res.status(500).send('Error downloading the matterbridge plugin config file');
813
670
  }
814
671
  });
815
672
  });
816
- // Endpoint to download the matterbridge backup (created with the backup command)
817
673
  this.expressApp.get('/api/download-backup', async (req, res) => {
818
674
  this.log.debug('The frontend sent /api/download-backup');
819
675
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
820
- /* istanbul ignore if */
821
676
  if (error) {
822
677
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
823
678
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
824
679
  }
825
680
  });
826
681
  });
827
- // Endpoint to upload a package
828
682
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
829
683
  const { filename } = req.body;
830
684
  const file = req.file;
831
- /* istanbul ignore if */
832
685
  if (!file || !filename) {
833
686
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
834
687
  res.status(400).send('Invalid request: file and filename are required');
835
688
  return;
836
689
  }
837
690
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
838
- // Define the path where the plugin file will be saved
839
691
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
840
692
  try {
841
- // Move the uploaded file to the specified path
842
693
  const fs = await import('node:fs');
843
694
  await fs.promises.rename(file.path, filePath);
844
695
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
845
- // Install the plugin package
846
696
  if (filename.endsWith('.tgz')) {
847
- const { spawnCommand } = await import('./utils/spawn.js');
697
+ const { spawnCommand } = await import('./spawn.js');
848
698
  if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
849
699
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
850
700
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
@@ -870,7 +720,6 @@ export class Frontend extends EventEmitter {
870
720
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
871
721
  }
872
722
  });
873
- // Fallback for routing (must be the last route)
874
723
  this.expressApp.use((req, res) => {
875
724
  const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
876
725
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
@@ -881,16 +730,13 @@ export class Frontend extends EventEmitter {
881
730
  async stop() {
882
731
  this.log.debug('Stopping the frontend...');
883
732
  const ws = await import('ws');
884
- // Remove listeners from the express app
885
733
  if (this.expressApp) {
886
734
  this.expressApp.removeAllListeners();
887
735
  this.expressApp = undefined;
888
736
  this.log.debug('Frontend app closed successfully');
889
737
  }
890
- // Close the WebSocket server
891
738
  if (this.webSocketServer) {
892
739
  this.log.debug('Closing WebSocket server...');
893
- // Close all active connections
894
740
  this.webSocketServer.clients.forEach((client) => {
895
741
  if (client.readyState === ws.WebSocket.OPEN) {
896
742
  client.close();
@@ -898,9 +744,7 @@ export class Frontend extends EventEmitter {
898
744
  });
899
745
  await withTimeout(new Promise((resolve) => {
900
746
  this.webSocketServer?.close((error) => {
901
- // istanbul ignore if
902
747
  if (error) {
903
- // istanbul ignore next
904
748
  this.log.error(`Error closing WebSocket server: ${error}`);
905
749
  }
906
750
  else {
@@ -913,27 +757,8 @@ export class Frontend extends EventEmitter {
913
757
  this.webSocketServer.removeAllListeners();
914
758
  this.webSocketServer = undefined;
915
759
  }
916
- // Close the http server
917
760
  if (this.httpServer) {
918
761
  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
762
  this.httpServer.close();
938
763
  this.log.debug('Http server closed successfully');
939
764
  this.listening = false;
@@ -942,27 +767,8 @@ export class Frontend extends EventEmitter {
942
767
  this.httpServer = undefined;
943
768
  this.log.debug('Frontend http server closed successfully');
944
769
  }
945
- // Close the https server
946
770
  if (this.httpsServer) {
947
771
  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
772
  this.httpsServer.close();
967
773
  this.log.debug('Https server closed successfully');
968
774
  this.listening = false;
@@ -973,13 +779,7 @@ export class Frontend extends EventEmitter {
973
779
  }
974
780
  this.log.debug('Frontend stopped successfully');
975
781
  }
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
782
  async getApiSettings() {
982
- // Update the variable system information properties
983
783
  this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
984
784
  this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
985
785
  this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -989,7 +789,6 @@ export class Frontend extends EventEmitter {
989
789
  this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
990
790
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
991
791
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
992
- // Create the matterbridge information
993
792
  const info = {
994
793
  homeDirectory: this.matterbridge.homeDirectory,
995
794
  rootDirectory: this.matterbridge.rootDirectory,
@@ -1025,15 +824,9 @@ export class Frontend extends EventEmitter {
1025
824
  };
1026
825
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
1027
826
  }
1028
- /**
1029
- * Retrieves the reachable attribute.
1030
- *
1031
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
1032
- * @returns {boolean} The reachable attribute.
1033
- */
1034
827
  getReachability(device) {
1035
828
  if (this.matterbridge.hasCleanupStarted)
1036
- return false; // Skip if cleanup has started
829
+ return false;
1037
830
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1038
831
  return false;
1039
832
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -1044,15 +837,9 @@ export class Frontend extends EventEmitter {
1044
837
  return true;
1045
838
  return false;
1046
839
  }
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
840
  getPowerSource(endpoint) {
1054
841
  if (this.matterbridge.hasCleanupStarted)
1055
- return undefined; // Skip if cleanup has started
842
+ return undefined;
1056
843
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1057
844
  return undefined;
1058
845
  const powerSource = (device) => {
@@ -1067,25 +854,16 @@ export class Frontend extends EventEmitter {
1067
854
  }
1068
855
  return;
1069
856
  };
1070
- // Root endpoint
1071
857
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1072
858
  return powerSource(endpoint);
1073
- // Child endpoints
1074
859
  for (const child of endpoint.getChildEndpoints()) {
1075
- // istanbul ignore else
1076
860
  if (child.hasClusterServer(PowerSource.Cluster.id))
1077
861
  return powerSource(child);
1078
862
  }
1079
863
  }
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
864
  getBatteryLevel(endpoint) {
1087
865
  if (this.matterbridge.hasCleanupStarted)
1088
- return undefined; // Skip if cleanup has started
866
+ return undefined;
1089
867
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1090
868
  return undefined;
1091
869
  const batteryLevel = (device) => {
@@ -1096,27 +874,16 @@ export class Frontend extends EventEmitter {
1096
874
  }
1097
875
  return undefined;
1098
876
  };
1099
- // Root endpoint
1100
877
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
1101
878
  return batteryLevel(endpoint);
1102
- // Child endpoints
1103
879
  for (const child of endpoint.getChildEndpoints()) {
1104
- // istanbul ignore else
1105
880
  if (child.hasClusterServer(PowerSource.Cluster.id))
1106
881
  return batteryLevel(child);
1107
882
  }
1108
883
  }
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
884
  getClusterTextFromDevice(device) {
1117
885
  if (this.matterbridge.hasCleanupStarted)
1118
- return ''; // Skip if cleanup has started
1119
- // istanbul ignore else
886
+ return '';
1120
887
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1121
888
  return '';
1122
889
  const getUserLabel = (device) => {
@@ -1126,7 +893,6 @@ export class Frontend extends EventEmitter {
1126
893
  if (composed)
1127
894
  return 'Composed: ' + composed.value;
1128
895
  }
1129
- // istanbul ignore next cause is not reachable
1130
896
  return '';
1131
897
  };
1132
898
  const getFixedLabel = (device) => {
@@ -1136,13 +902,11 @@ export class Frontend extends EventEmitter {
1136
902
  if (composed)
1137
903
  return 'Composed: ' + composed.value;
1138
904
  }
1139
- // istanbul ignore next cause is not reacheable
1140
905
  return '';
1141
906
  };
1142
907
  let attributes = '';
1143
908
  let supportedModes = [];
1144
909
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1145
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
1146
910
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1147
911
  return;
1148
912
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1232,17 +996,11 @@ export class Frontend extends EventEmitter {
1232
996
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1233
997
  attributes += `${getUserLabel(device)} `;
1234
998
  });
1235
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1236
999
  return attributes.trimStart().trimEnd();
1237
1000
  }
1238
- /**
1239
- * Retrieves the registered plugins sanitized for res.json().
1240
- *
1241
- * @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
1242
- */
1243
1001
  getPlugins() {
1244
1002
  if (this.matterbridge.hasCleanupStarted)
1245
- return []; // Skip if cleanup has started
1003
+ return [];
1246
1004
  const plugins = [];
1247
1005
  for (const plugin of this.matterbridge.plugins.array()) {
1248
1006
  plugins.push({
@@ -1270,27 +1028,18 @@ export class Frontend extends EventEmitter {
1270
1028
  schemaJson: plugin.schemaJson,
1271
1029
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1272
1030
  hasBlackList: plugin.configJson?.blackList !== undefined,
1273
- // Childbridge mode specific data
1274
1031
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1275
1032
  });
1276
1033
  }
1277
1034
  return plugins;
1278
1035
  }
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
1036
  getDevices(pluginName) {
1286
1037
  if (this.matterbridge.hasCleanupStarted)
1287
- return []; // Skip if cleanup has started
1038
+ return [];
1288
1039
  const devices = [];
1289
1040
  for (const device of this.matterbridge.devices.array()) {
1290
- // Filter by pluginName if provided
1291
1041
  if (pluginName && pluginName !== device.plugin)
1292
1042
  continue;
1293
- // Check if the device has the required properties
1294
1043
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1295
1044
  continue;
1296
1045
  devices.push({
@@ -1311,39 +1060,24 @@ export class Frontend extends EventEmitter {
1311
1060
  }
1312
1061
  return devices;
1313
1062
  }
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
1063
  getClusters(pluginName, endpointNumber) {
1324
1064
  if (this.matterbridge.hasCleanupStarted)
1325
- return; // Skip if cleanup has started
1065
+ return;
1326
1066
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1327
1067
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1328
1068
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1329
1069
  return;
1330
1070
  }
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
1071
  const deviceTypes = [];
1334
1072
  const clusters = [];
1335
1073
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1336
1074
  deviceTypes.push(d.deviceType);
1337
1075
  });
1338
- // Get the clusters from the main endpoint
1339
1076
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1340
1077
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1341
1078
  return;
1342
1079
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1343
1080
  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
1081
  clusters.push({
1348
1082
  endpoint: endpoint.number.toString(),
1349
1083
  number: endpoint.number,
@@ -1357,19 +1091,12 @@ export class Frontend extends EventEmitter {
1357
1091
  attributeLocalValue: attributeValue,
1358
1092
  });
1359
1093
  });
1360
- // Get the child endpoints
1361
1094
  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
1095
  childEndpoints.forEach((childEndpoint) => {
1366
- // istanbul ignore if cause is not reachable: should never happen but ...
1367
1096
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1368
1097
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1369
1098
  return;
1370
1099
  }
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
1100
  const deviceTypes = [];
1374
1101
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1375
1102
  deviceTypes.push(d.deviceType);
@@ -1379,9 +1106,6 @@ export class Frontend extends EventEmitter {
1379
1106
  return;
1380
1107
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1381
1108
  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
1109
  clusters.push({
1386
1110
  endpoint: childEndpoint.number.toString(),
1387
1111
  number: childEndpoint.number,
@@ -1401,7 +1125,6 @@ export class Frontend extends EventEmitter {
1401
1125
  async generateDiagnostic() {
1402
1126
  this.log.debug('Generating diagnostic...');
1403
1127
  const serverNodes = [];
1404
- // istanbul ignore else
1405
1128
  if (this.matterbridge.bridgeMode === 'bridge') {
1406
1129
  if (this.matterbridge.serverNode)
1407
1130
  serverNodes.push(this.matterbridge.serverNode);
@@ -1412,7 +1135,6 @@ export class Frontend extends EventEmitter {
1412
1135
  serverNodes.push(plugin.serverNode);
1413
1136
  }
1414
1137
  }
1415
- // istanbul ignore next
1416
1138
  for (const device of this.matterbridge.devices.array()) {
1417
1139
  if (device.serverNode)
1418
1140
  serverNodes.push(device.serverNode);
@@ -1436,15 +1158,8 @@ export class Frontend extends EventEmitter {
1436
1158
  values: [...serverNodes],
1437
1159
  })));
1438
1160
  delete Logger.destinations.diagnostic;
1439
- await wait(500); // Wait for the log to be written
1161
+ await wait(500);
1440
1162
  }
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
1163
  async wsMessageHandler(client, message) {
1449
1164
  let data;
1450
1165
  const sendResponse = (data) => {
@@ -1462,13 +1177,12 @@ export class Frontend extends EventEmitter {
1462
1177
  client.send(JSON.stringify(data));
1463
1178
  }
1464
1179
  else {
1465
- // istanbul ignore next cause is only a safety check
1466
1180
  this.log.error('Cannot send api response, client not connected');
1467
1181
  }
1468
1182
  };
1469
1183
  try {
1470
1184
  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') {
1185
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1472
1186
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1473
1187
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1474
1188
  return;
@@ -1525,22 +1239,7 @@ export class Frontend extends EventEmitter {
1525
1239
  }
1526
1240
  this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1527
1241
  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
- */
1242
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1544
1243
  const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1545
1244
  if (plugin) {
1546
1245
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1553,7 +1252,7 @@ export class Frontend extends EventEmitter {
1553
1252
  this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1554
1253
  return;
1555
1254
  })
1556
- .catch(/* istanbul ignore next */ (_error) => { });
1255
+ .catch((_error) => { });
1557
1256
  }
1558
1257
  else {
1559
1258
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
@@ -1567,10 +1266,6 @@ export class Frontend extends EventEmitter {
1567
1266
  }
1568
1267
  this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1569
1268
  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
1269
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1575
1270
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1576
1271
  await this.matterbridge.plugins.remove(data.params.pluginName);
@@ -1598,10 +1293,8 @@ export class Frontend extends EventEmitter {
1598
1293
  this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
1599
1294
  setImmediate(async () => {
1600
1295
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
1601
- // @ts-expect-error Accessing private method
1602
1296
  if (plugin.serverNode)
1603
1297
  await this.matterbridge.startServerNode(plugin.serverNode);
1604
- // @ts-expect-error Accessing private method
1605
1298
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1606
1299
  await this.matterbridge.startServerNode(device.serverNode);
1607
1300
  this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
@@ -1617,11 +1310,9 @@ export class Frontend extends EventEmitter {
1617
1310
  }
1618
1311
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1619
1312
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
1620
- // @ts-expect-error Accessing private method
1621
1313
  await this.matterbridge.stopServerNode(device.serverNode);
1622
1314
  device.serverNode = undefined;
1623
1315
  }
1624
- // @ts-expect-error Accessing private method
1625
1316
  if (plugin.serverNode)
1626
1317
  await this.matterbridge.stopServerNode(plugin.serverNode);
1627
1318
  plugin.serverNode = undefined;
@@ -1640,37 +1331,30 @@ export class Frontend extends EventEmitter {
1640
1331
  this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
1641
1332
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1642
1333
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1643
- // Stop server nodes
1644
1334
  if (plugin.serverNode) {
1645
- // @ts-expect-error Accessing private method
1646
1335
  await this.matterbridge.stopServerNode(plugin.serverNode);
1647
1336
  plugin.serverNode = undefined;
1648
1337
  }
1649
1338
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
1650
- // @ts-expect-error Accessing private method
1651
1339
  if (device.serverNode)
1652
1340
  await this.matterbridge.stopServerNode(device.serverNode);
1653
1341
  device.serverNode = undefined;
1654
1342
  this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
1655
1343
  this.matterbridge.devices.remove(device);
1656
1344
  }
1657
- // @ts-expect-error Accessing private method
1658
1345
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1659
1346
  await this.matterbridge.createDynamicPlugin(plugin);
1660
1347
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1661
- plugin.restartRequired = false; // Reset plugin restartRequired
1348
+ plugin.restartRequired = false;
1662
1349
  let needRestart = 0;
1663
1350
  for (const plugin of this.matterbridge.plugins) {
1664
1351
  if (plugin.restartRequired)
1665
1352
  needRestart++;
1666
1353
  }
1667
1354
  if (needRestart === 0)
1668
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1669
- // Start server nodes
1670
- // @ts-expect-error Accessing private method
1355
+ this.wssSendRestartNotRequired(true);
1671
1356
  if (plugin.serverNode)
1672
1357
  await this.matterbridge.startServerNode(plugin.serverNode);
1673
- // @ts-expect-error Accessing private method
1674
1358
  for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1675
1359
  await this.matterbridge.startServerNode(device.serverNode);
1676
1360
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1815,9 +1499,6 @@ export class Frontend extends EventEmitter {
1815
1499
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
1816
1500
  }
1817
1501
  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
1502
  const advertiser = serverNode.env.get(DeviceAdvertiser);
1822
1503
  if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
1823
1504
  await advertiser.advertise(true);
@@ -1881,7 +1562,6 @@ export class Frontend extends EventEmitter {
1881
1562
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
1882
1563
  return;
1883
1564
  }
1884
- // istanbul ignore next
1885
1565
  const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1886
1566
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
1887
1567
  }
@@ -1895,7 +1575,6 @@ export class Frontend extends EventEmitter {
1895
1575
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
1896
1576
  return;
1897
1577
  }
1898
- // istanbul ignore next
1899
1578
  const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1900
1579
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
1901
1580
  }
@@ -1947,22 +1626,22 @@ export class Frontend extends EventEmitter {
1947
1626
  if (isValidString(data.params.value, 4)) {
1948
1627
  this.log.debug('Matterbridge logger level:', data.params.value);
1949
1628
  if (data.params.value === 'Debug') {
1950
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1629
+ await this.matterbridge.setLogLevel("debug");
1951
1630
  }
1952
1631
  else if (data.params.value === 'Info') {
1953
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1632
+ await this.matterbridge.setLogLevel("info");
1954
1633
  }
1955
1634
  else if (data.params.value === 'Notice') {
1956
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1635
+ await this.matterbridge.setLogLevel("notice");
1957
1636
  }
1958
1637
  else if (data.params.value === 'Warn') {
1959
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1638
+ await this.matterbridge.setLogLevel("warn");
1960
1639
  }
1961
1640
  else if (data.params.value === 'Error') {
1962
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1641
+ await this.matterbridge.setLogLevel("error");
1963
1642
  }
1964
1643
  else if (data.params.value === 'Fatal') {
1965
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1644
+ await this.matterbridge.setLogLevel("fatal");
1966
1645
  }
1967
1646
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1968
1647
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1973,7 +1652,6 @@ export class Frontend extends EventEmitter {
1973
1652
  this.log.debug('Matterbridge file log:', data.params.value);
1974
1653
  this.matterbridge.fileLogger = data.params.value;
1975
1654
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1976
- // Create the file logger for matterbridge
1977
1655
  if (data.params.value)
1978
1656
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1979
1657
  else
@@ -2003,12 +1681,11 @@ export class Frontend extends EventEmitter {
2003
1681
  Logger.level = MatterLogLevel.FATAL;
2004
1682
  }
2005
1683
  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 */;
1684
+ let callbackLogLevel = "notice";
1685
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1686
+ callbackLogLevel = "info";
1687
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
1688
+ callbackLogLevel = "debug";
2012
1689
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
2013
1690
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
2014
1691
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
@@ -2060,7 +1737,6 @@ export class Frontend extends EventEmitter {
2060
1737
  }
2061
1738
  break;
2062
1739
  case 'setmatterport':
2063
- // eslint-disable-next-line no-case-declarations
2064
1740
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2065
1741
  if (isValidNumber(port, 5540, 5600)) {
2066
1742
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
@@ -2080,7 +1756,6 @@ export class Frontend extends EventEmitter {
2080
1756
  }
2081
1757
  break;
2082
1758
  case 'setmatterdiscriminator':
2083
- // eslint-disable-next-line no-case-declarations
2084
1759
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2085
1760
  if (isValidNumber(discriminator, 0, 4095)) {
2086
1761
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
@@ -2100,7 +1775,6 @@ export class Frontend extends EventEmitter {
2100
1775
  }
2101
1776
  break;
2102
1777
  case 'setmatterpasscode':
2103
- // eslint-disable-next-line no-case-declarations
2104
1778
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
2105
1779
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
2106
1780
  this.matterbridge.passcode = passcode;
@@ -2146,19 +1820,15 @@ export class Frontend extends EventEmitter {
2146
1820
  return;
2147
1821
  }
2148
1822
  const config = plugin.configJson;
2149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2150
1823
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2151
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2152
1824
  if (select === 'serial')
2153
1825
  this.log.info(`Selected device serial ${data.params.serial}`);
2154
1826
  if (select === 'name')
2155
1827
  this.log.info(`Selected device name ${data.params.name}`);
2156
1828
  if (config && select && (select === 'serial' || select === 'name')) {
2157
- // Remove postfix from the serial if it exists
2158
1829
  if (config.postfix) {
2159
1830
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2160
1831
  }
2161
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
2162
1832
  if (isValidArray(config.whiteList, 1)) {
2163
1833
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
2164
1834
  config.whiteList.push(data.params.serial);
@@ -2167,7 +1837,6 @@ export class Frontend extends EventEmitter {
2167
1837
  config.whiteList.push(data.params.name);
2168
1838
  }
2169
1839
  }
2170
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
2171
1840
  if (isValidArray(config.blackList, 1)) {
2172
1841
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
2173
1842
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -2195,9 +1864,7 @@ export class Frontend extends EventEmitter {
2195
1864
  return;
2196
1865
  }
2197
1866
  const config = plugin.configJson;
2198
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2199
1867
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
2200
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
2201
1868
  if (select === 'serial')
2202
1869
  this.log.info(`Unselected device serial ${data.params.serial}`);
2203
1870
  if (select === 'name')
@@ -2206,7 +1873,6 @@ export class Frontend extends EventEmitter {
2206
1873
  if (config.postfix) {
2207
1874
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2208
1875
  }
2209
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2210
1876
  if (isValidArray(config.whiteList, 1)) {
2211
1877
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2212
1878
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -2215,7 +1881,6 @@ export class Frontend extends EventEmitter {
2215
1881
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
2216
1882
  }
2217
1883
  }
2218
- // Add the serial to the blackList
2219
1884
  if (isValidArray(config.blackList)) {
2220
1885
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2221
1886
  config.blackList.push(data.params.serial);
@@ -2238,7 +1903,6 @@ export class Frontend extends EventEmitter {
2238
1903
  }
2239
1904
  }
2240
1905
  else {
2241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2242
1906
  const localData = data;
2243
1907
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
2244
1908
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -2248,46 +1912,23 @@ export class Frontend extends EventEmitter {
2248
1912
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2249
1913
  }
2250
1914
  }
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
1915
  wssSendLogMessage(level, time, name, message) {
2265
1916
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2266
1917
  return;
2267
1918
  if (!level || !time || !name || !message)
2268
1919
  return;
2269
- // Remove ANSI escape codes from the message
2270
- // eslint-disable-next-line no-control-regex
2271
1920
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2272
- // Remove leading asterisks from the message
2273
1921
  message = message.replace(/^\*+/, '');
2274
- // Replace all occurrences of \t and \n
2275
1922
  message = message.replace(/[\t\n]/g, '');
2276
- // Remove non-printable characters
2277
- // eslint-disable-next-line no-control-regex
2278
1923
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2279
- // Replace all occurrences of \" with "
2280
1924
  message = message.replace(/\\"/g, '"');
2281
- // Define the maximum allowed length for continuous characters without a space
2282
1925
  const maxContinuousLength = 100;
2283
1926
  const keepStartLength = 20;
2284
1927
  const keepEndLength = 20;
2285
- // Split the message into words
2286
1928
  if (level !== 'spawn') {
2287
1929
  message = message
2288
1930
  .split(' ')
2289
1931
  .map((word) => {
2290
- // If the word length exceeds the max continuous length, insert spaces and truncate
2291
1932
  if (word.length > maxContinuousLength) {
2292
1933
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2293
1934
  }
@@ -2295,34 +1936,14 @@ export class Frontend extends EventEmitter {
2295
1936
  })
2296
1937
  .join(' ');
2297
1938
  }
2298
- // Send the message to all connected clients
2299
1939
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2300
1940
  }
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
1941
  wssSendRefreshRequired(changed, params) {
2314
1942
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2315
1943
  return;
2316
1944
  this.log.debug('Sending a refresh required message to all connected clients');
2317
- // Send the message to all connected clients
2318
1945
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2319
1946
  }
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
1947
  wssSendRestartRequired(snackbar = true, fixed = false) {
2327
1948
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2328
1949
  return;
@@ -2331,14 +1952,8 @@ export class Frontend extends EventEmitter {
2331
1952
  this.matterbridge.fixedRestartRequired = fixed;
2332
1953
  if (snackbar === true)
2333
1954
  this.wssSendSnackbarMessage(`Restart required`, 0);
2334
- // Send the message to all connected clients
2335
1955
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2336
1956
  }
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
1957
  wssSendRestartNotRequired(snackbar = true) {
2343
1958
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2344
1959
  return;
@@ -2346,145 +1961,64 @@ export class Frontend extends EventEmitter {
2346
1961
  this.matterbridge.restartRequired = false;
2347
1962
  if (snackbar === true)
2348
1963
  this.wssSendCloseSnackbarMessage(`Restart required`);
2349
- // Send the message to all connected clients
2350
1964
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2351
1965
  }
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
1966
  wssSendUpdateRequired(devVersion = false) {
2358
1967
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2359
1968
  return;
2360
1969
  this.log.debug('Sending an update required message to all connected clients');
2361
1970
  this.matterbridge.updateRequired = true;
2362
- // Send the message to all connected clients
2363
1971
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2364
1972
  }
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
1973
  wssSendCpuUpdate(cpuUsage, processCpuUsage) {
2372
1974
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2373
1975
  return;
2374
- // istanbul ignore else
2375
1976
  if (hasParameter('debug'))
2376
1977
  this.log.debug('Sending a cpu update message to all connected clients');
2377
- // Send the message to all connected clients
2378
1978
  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
1979
  }
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
1980
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2392
1981
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2393
1982
  return;
2394
- // istanbul ignore else
2395
1983
  if (hasParameter('debug'))
2396
1984
  this.log.debug('Sending a memory update message to all connected clients');
2397
- // Send the message to all connected clients
2398
1985
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2399
1986
  }
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
1987
  wssSendUptimeUpdate(systemUptime, processUptime) {
2407
1988
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2408
1989
  return;
2409
- // istanbul ignore else
2410
1990
  if (hasParameter('debug'))
2411
1991
  this.log.debug('Sending a uptime update message to all connected clients');
2412
- // Send the message to all connected clients
2413
1992
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2414
1993
  }
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
1994
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2427
1995
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2428
1996
  return;
2429
1997
  this.log.debug('Sending a snackbar message to all connected clients');
2430
- // Send the message to all connected clients
2431
1998
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2432
1999
  }
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
2000
  wssSendCloseSnackbarMessage(message) {
2440
2001
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2441
2002
  return;
2442
2003
  this.log.debug('Sending a close snackbar message to all connected clients');
2443
- // Send the message to all connected clients
2444
2004
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2445
2005
  }
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
2006
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2463
2007
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2464
2008
  return;
2465
2009
  this.log.debug('Sending an attribute update message to all connected clients');
2466
- // Send the message to all connected clients
2467
2010
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2468
2011
  }
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
2012
  wssBroadcastMessage(msg) {
2476
2013
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2477
2014
  return;
2478
- // Send the message to all connected clients
2479
2015
  const stringifiedMsg = JSON.stringify(msg);
2480
2016
  if (msg.method !== 'log')
2481
2017
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2482
2018
  this.webSocketServer?.clients.forEach((client) => {
2483
- // istanbul ignore else
2484
2019
  if (client.readyState === client.OPEN) {
2485
2020
  client.send(stringifiedMsg);
2486
2021
  }
2487
2022
  });
2488
2023
  }
2489
2024
  }
2490
- //# sourceMappingURL=frontend.js.map