matterbridge 3.2.7 → 3.2.8-dev-20250916-967e0b8

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 (291) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/dist/cli.js +2 -91
  3. package/dist/cliEmitter.js +0 -30
  4. package/dist/clusters/export.js +0 -2
  5. package/dist/defaultConfigSchema.js +0 -24
  6. package/dist/deviceManager.js +1 -94
  7. package/dist/devices/airConditioner.js +0 -57
  8. package/dist/devices/batteryStorage.js +1 -48
  9. package/dist/devices/cooktop.js +0 -55
  10. package/dist/devices/dishwasher.js +0 -57
  11. package/dist/devices/evse.js +10 -74
  12. package/dist/devices/export.js +0 -5
  13. package/dist/devices/extractorHood.js +0 -42
  14. package/dist/devices/heatPump.js +2 -50
  15. package/dist/devices/laundryDryer.js +3 -62
  16. package/dist/devices/laundryWasher.js +4 -70
  17. package/dist/devices/microwaveOven.js +5 -88
  18. package/dist/devices/oven.js +0 -85
  19. package/dist/devices/refrigerator.js +0 -102
  20. package/dist/devices/roboticVacuumCleaner.js +9 -100
  21. package/dist/devices/solarPower.js +0 -38
  22. package/dist/devices/speaker.js +0 -84
  23. package/dist/devices/temperatureControl.js +3 -25
  24. package/dist/devices/waterHeater.js +2 -82
  25. package/dist/dgram/coap.js +13 -126
  26. package/dist/dgram/dgram.js +2 -114
  27. package/dist/dgram/mb_coap.js +3 -41
  28. package/dist/dgram/mb_mdns.js +15 -80
  29. package/dist/dgram/mdns.js +137 -299
  30. package/dist/dgram/multicast.js +1 -62
  31. package/dist/dgram/unicast.js +0 -54
  32. package/dist/frontend.js +34 -459
  33. package/dist/globalMatterbridge.js +0 -47
  34. package/dist/helpers.js +0 -53
  35. package/dist/index.js +1 -30
  36. package/dist/logger/export.js +0 -1
  37. package/dist/matter/behaviors.js +0 -2
  38. package/dist/matter/clusters.js +0 -2
  39. package/dist/matter/devices.js +0 -2
  40. package/dist/matter/endpoints.js +0 -2
  41. package/dist/matter/export.js +0 -3
  42. package/dist/matter/types.js +0 -3
  43. package/dist/matterbridge.js +53 -789
  44. package/dist/matterbridgeAccessoryPlatform.js +0 -36
  45. package/dist/matterbridgeBehaviors.js +5 -65
  46. package/dist/matterbridgeDeviceTypes.js +17 -630
  47. package/dist/matterbridgeDynamicPlatform.js +0 -36
  48. package/dist/matterbridgeEndpoint.js +55 -1373
  49. package/dist/matterbridgeEndpointHelpers.js +12 -345
  50. package/dist/matterbridgePlatform.js +0 -304
  51. package/dist/matterbridgeTypes.js +0 -25
  52. package/dist/pluginManager.js +3 -249
  53. package/dist/shelly.js +7 -168
  54. package/dist/storage/export.js +0 -1
  55. package/dist/update.js +0 -69
  56. package/dist/utils/colorUtils.js +2 -97
  57. package/dist/utils/commandLine.js +0 -54
  58. package/dist/utils/copyDirectory.js +1 -38
  59. package/dist/utils/createDirectory.js +0 -33
  60. package/dist/utils/createZip.js +2 -47
  61. package/dist/utils/deepCopy.js +0 -39
  62. package/dist/utils/deepEqual.js +1 -72
  63. package/dist/utils/error.js +0 -41
  64. package/dist/utils/export.js +0 -1
  65. package/dist/utils/hex.js +0 -124
  66. package/dist/utils/isvalid.js +0 -101
  67. package/dist/utils/jestHelpers.js +3 -153
  68. package/dist/utils/network.js +5 -91
  69. package/dist/utils/spawn.js +0 -40
  70. package/dist/utils/wait.js +8 -60
  71. package/frontend/build/asset-manifest.json +6 -8
  72. package/frontend/build/index.html +1 -1
  73. package/frontend/build/static/css/{main.a2f4846a.css → main.56c16a87.css} +2 -2
  74. package/frontend/build/static/css/main.56c16a87.css.map +1 -0
  75. package/frontend/build/static/js/main.c1ca9eaf.js +3 -0
  76. package/frontend/build/static/js/{main.ee68a4ae.js.map → main.c1ca9eaf.js.map} +1 -1
  77. package/frontend/package.json +8 -11
  78. package/npm-shrinkwrap.json +5 -5
  79. package/package.json +1 -2
  80. package/dist/cli.d.ts +0 -26
  81. package/dist/cli.d.ts.map +0 -1
  82. package/dist/cli.js.map +0 -1
  83. package/dist/cliEmitter.d.ts +0 -34
  84. package/dist/cliEmitter.d.ts.map +0 -1
  85. package/dist/cliEmitter.js.map +0 -1
  86. package/dist/clusters/export.d.ts +0 -2
  87. package/dist/clusters/export.d.ts.map +0 -1
  88. package/dist/clusters/export.js.map +0 -1
  89. package/dist/defaultConfigSchema.d.ts +0 -28
  90. package/dist/defaultConfigSchema.d.ts.map +0 -1
  91. package/dist/defaultConfigSchema.js.map +0 -1
  92. package/dist/deviceManager.d.ts +0 -112
  93. package/dist/deviceManager.d.ts.map +0 -1
  94. package/dist/deviceManager.js.map +0 -1
  95. package/dist/devices/airConditioner.d.ts +0 -98
  96. package/dist/devices/airConditioner.d.ts.map +0 -1
  97. package/dist/devices/airConditioner.js.map +0 -1
  98. package/dist/devices/batteryStorage.d.ts +0 -48
  99. package/dist/devices/batteryStorage.d.ts.map +0 -1
  100. package/dist/devices/batteryStorage.js.map +0 -1
  101. package/dist/devices/cooktop.d.ts +0 -60
  102. package/dist/devices/cooktop.d.ts.map +0 -1
  103. package/dist/devices/cooktop.js.map +0 -1
  104. package/dist/devices/dishwasher.d.ts +0 -71
  105. package/dist/devices/dishwasher.d.ts.map +0 -1
  106. package/dist/devices/dishwasher.js.map +0 -1
  107. package/dist/devices/evse.d.ts +0 -75
  108. package/dist/devices/evse.d.ts.map +0 -1
  109. package/dist/devices/evse.js.map +0 -1
  110. package/dist/devices/export.d.ts +0 -17
  111. package/dist/devices/export.d.ts.map +0 -1
  112. package/dist/devices/export.js.map +0 -1
  113. package/dist/devices/extractorHood.d.ts +0 -46
  114. package/dist/devices/extractorHood.d.ts.map +0 -1
  115. package/dist/devices/extractorHood.js.map +0 -1
  116. package/dist/devices/heatPump.d.ts +0 -47
  117. package/dist/devices/heatPump.d.ts.map +0 -1
  118. package/dist/devices/heatPump.js.map +0 -1
  119. package/dist/devices/laundryDryer.d.ts +0 -67
  120. package/dist/devices/laundryDryer.d.ts.map +0 -1
  121. package/dist/devices/laundryDryer.js.map +0 -1
  122. package/dist/devices/laundryWasher.d.ts +0 -81
  123. package/dist/devices/laundryWasher.d.ts.map +0 -1
  124. package/dist/devices/laundryWasher.js.map +0 -1
  125. package/dist/devices/microwaveOven.d.ts +0 -168
  126. package/dist/devices/microwaveOven.d.ts.map +0 -1
  127. package/dist/devices/microwaveOven.js.map +0 -1
  128. package/dist/devices/oven.d.ts +0 -105
  129. package/dist/devices/oven.d.ts.map +0 -1
  130. package/dist/devices/oven.js.map +0 -1
  131. package/dist/devices/refrigerator.d.ts +0 -118
  132. package/dist/devices/refrigerator.d.ts.map +0 -1
  133. package/dist/devices/refrigerator.js.map +0 -1
  134. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  135. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  136. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  137. package/dist/devices/solarPower.d.ts +0 -40
  138. package/dist/devices/solarPower.d.ts.map +0 -1
  139. package/dist/devices/solarPower.js.map +0 -1
  140. package/dist/devices/speaker.d.ts +0 -87
  141. package/dist/devices/speaker.d.ts.map +0 -1
  142. package/dist/devices/speaker.js.map +0 -1
  143. package/dist/devices/temperatureControl.d.ts +0 -166
  144. package/dist/devices/temperatureControl.d.ts.map +0 -1
  145. package/dist/devices/temperatureControl.js.map +0 -1
  146. package/dist/devices/waterHeater.d.ts +0 -111
  147. package/dist/devices/waterHeater.d.ts.map +0 -1
  148. package/dist/devices/waterHeater.js.map +0 -1
  149. package/dist/dgram/coap.d.ts +0 -205
  150. package/dist/dgram/coap.d.ts.map +0 -1
  151. package/dist/dgram/coap.js.map +0 -1
  152. package/dist/dgram/dgram.d.ts +0 -141
  153. package/dist/dgram/dgram.d.ts.map +0 -1
  154. package/dist/dgram/dgram.js.map +0 -1
  155. package/dist/dgram/mb_coap.d.ts +0 -24
  156. package/dist/dgram/mb_coap.d.ts.map +0 -1
  157. package/dist/dgram/mb_coap.js.map +0 -1
  158. package/dist/dgram/mb_mdns.d.ts +0 -24
  159. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  160. package/dist/dgram/mb_mdns.js.map +0 -1
  161. package/dist/dgram/mdns.d.ts +0 -290
  162. package/dist/dgram/mdns.d.ts.map +0 -1
  163. package/dist/dgram/mdns.js.map +0 -1
  164. package/dist/dgram/multicast.d.ts +0 -67
  165. package/dist/dgram/multicast.d.ts.map +0 -1
  166. package/dist/dgram/multicast.js.map +0 -1
  167. package/dist/dgram/unicast.d.ts +0 -56
  168. package/dist/dgram/unicast.d.ts.map +0 -1
  169. package/dist/dgram/unicast.js.map +0 -1
  170. package/dist/frontend.d.ts +0 -315
  171. package/dist/frontend.d.ts.map +0 -1
  172. package/dist/frontend.js.map +0 -1
  173. package/dist/globalMatterbridge.d.ts +0 -59
  174. package/dist/globalMatterbridge.d.ts.map +0 -1
  175. package/dist/globalMatterbridge.js.map +0 -1
  176. package/dist/helpers.d.ts +0 -48
  177. package/dist/helpers.d.ts.map +0 -1
  178. package/dist/helpers.js.map +0 -1
  179. package/dist/index.d.ts +0 -33
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/logger/export.d.ts +0 -2
  183. package/dist/logger/export.d.ts.map +0 -1
  184. package/dist/logger/export.js.map +0 -1
  185. package/dist/matter/behaviors.d.ts +0 -2
  186. package/dist/matter/behaviors.d.ts.map +0 -1
  187. package/dist/matter/behaviors.js.map +0 -1
  188. package/dist/matter/clusters.d.ts +0 -2
  189. package/dist/matter/clusters.d.ts.map +0 -1
  190. package/dist/matter/clusters.js.map +0 -1
  191. package/dist/matter/devices.d.ts +0 -2
  192. package/dist/matter/devices.d.ts.map +0 -1
  193. package/dist/matter/devices.js.map +0 -1
  194. package/dist/matter/endpoints.d.ts +0 -2
  195. package/dist/matter/endpoints.d.ts.map +0 -1
  196. package/dist/matter/endpoints.js.map +0 -1
  197. package/dist/matter/export.d.ts +0 -5
  198. package/dist/matter/export.d.ts.map +0 -1
  199. package/dist/matter/export.js.map +0 -1
  200. package/dist/matter/types.d.ts +0 -3
  201. package/dist/matter/types.d.ts.map +0 -1
  202. package/dist/matter/types.js.map +0 -1
  203. package/dist/matterbridge.d.ts +0 -465
  204. package/dist/matterbridge.d.ts.map +0 -1
  205. package/dist/matterbridge.js.map +0 -1
  206. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  207. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  208. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  209. package/dist/matterbridgeBehaviors.d.ts +0 -1747
  210. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  211. package/dist/matterbridgeBehaviors.js.map +0 -1
  212. package/dist/matterbridgeDeviceTypes.d.ts +0 -761
  213. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  214. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  215. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  216. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  217. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  218. package/dist/matterbridgeEndpoint.d.ts +0 -1515
  219. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  220. package/dist/matterbridgeEndpoint.js.map +0 -1
  221. package/dist/matterbridgeEndpointHelpers.d.ts +0 -407
  222. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  223. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  224. package/dist/matterbridgePlatform.d.ts +0 -380
  225. package/dist/matterbridgePlatform.d.ts.map +0 -1
  226. package/dist/matterbridgePlatform.js.map +0 -1
  227. package/dist/matterbridgeTypes.d.ts +0 -204
  228. package/dist/matterbridgeTypes.d.ts.map +0 -1
  229. package/dist/matterbridgeTypes.js.map +0 -1
  230. package/dist/pluginManager.d.ts +0 -270
  231. package/dist/pluginManager.d.ts.map +0 -1
  232. package/dist/pluginManager.js.map +0 -1
  233. package/dist/shelly.d.ts +0 -174
  234. package/dist/shelly.d.ts.map +0 -1
  235. package/dist/shelly.js.map +0 -1
  236. package/dist/storage/export.d.ts +0 -2
  237. package/dist/storage/export.d.ts.map +0 -1
  238. package/dist/storage/export.js.map +0 -1
  239. package/dist/update.d.ts +0 -75
  240. package/dist/update.d.ts.map +0 -1
  241. package/dist/update.js.map +0 -1
  242. package/dist/utils/colorUtils.d.ts +0 -99
  243. package/dist/utils/colorUtils.d.ts.map +0 -1
  244. package/dist/utils/colorUtils.js.map +0 -1
  245. package/dist/utils/commandLine.d.ts +0 -59
  246. package/dist/utils/commandLine.d.ts.map +0 -1
  247. package/dist/utils/commandLine.js.map +0 -1
  248. package/dist/utils/copyDirectory.d.ts +0 -33
  249. package/dist/utils/copyDirectory.d.ts.map +0 -1
  250. package/dist/utils/copyDirectory.js.map +0 -1
  251. package/dist/utils/createDirectory.d.ts +0 -34
  252. package/dist/utils/createDirectory.d.ts.map +0 -1
  253. package/dist/utils/createDirectory.js.map +0 -1
  254. package/dist/utils/createZip.d.ts +0 -39
  255. package/dist/utils/createZip.d.ts.map +0 -1
  256. package/dist/utils/createZip.js.map +0 -1
  257. package/dist/utils/deepCopy.d.ts +0 -32
  258. package/dist/utils/deepCopy.d.ts.map +0 -1
  259. package/dist/utils/deepCopy.js.map +0 -1
  260. package/dist/utils/deepEqual.d.ts +0 -54
  261. package/dist/utils/deepEqual.d.ts.map +0 -1
  262. package/dist/utils/deepEqual.js.map +0 -1
  263. package/dist/utils/error.d.ts +0 -44
  264. package/dist/utils/error.d.ts.map +0 -1
  265. package/dist/utils/error.js.map +0 -1
  266. package/dist/utils/export.d.ts +0 -13
  267. package/dist/utils/export.d.ts.map +0 -1
  268. package/dist/utils/export.js.map +0 -1
  269. package/dist/utils/hex.d.ts +0 -89
  270. package/dist/utils/hex.d.ts.map +0 -1
  271. package/dist/utils/hex.js.map +0 -1
  272. package/dist/utils/isvalid.d.ts +0 -103
  273. package/dist/utils/isvalid.d.ts.map +0 -1
  274. package/dist/utils/isvalid.js.map +0 -1
  275. package/dist/utils/jestHelpers.d.ts +0 -137
  276. package/dist/utils/jestHelpers.d.ts.map +0 -1
  277. package/dist/utils/jestHelpers.js.map +0 -1
  278. package/dist/utils/network.d.ts +0 -84
  279. package/dist/utils/network.d.ts.map +0 -1
  280. package/dist/utils/network.js.map +0 -1
  281. package/dist/utils/spawn.d.ts +0 -33
  282. package/dist/utils/spawn.d.ts.map +0 -1
  283. package/dist/utils/spawn.js.map +0 -1
  284. package/dist/utils/wait.d.ts +0 -54
  285. package/dist/utils/wait.d.ts.map +0 -1
  286. package/dist/utils/wait.js.map +0 -1
  287. package/frontend/build/static/css/main.a2f4846a.css.map +0 -1
  288. package/frontend/build/static/js/453.d855a71b.chunk.js +0 -2
  289. package/frontend/build/static/js/453.d855a71b.chunk.js.map +0 -1
  290. package/frontend/build/static/js/main.ee68a4ae.js +0 -3
  291. /package/frontend/build/static/js/{main.ee68a4ae.js.LICENSE.txt → main.c1ca9eaf.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,27 +1,3 @@
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.2.0
8
- * @license Apache-2.0
9
- *
10
- * Copyright 2025, 2026, 2027 Luca Liguori.
11
- *
12
- * Licensed under the Apache License, Version 2.0 (the "License");
13
- * you may not use this file except in compliance with the License.
14
- * You may obtain a copy of the License at
15
- *
16
- * http://www.apache.org/licenses/LICENSE-2.0
17
- *
18
- * Unless required by applicable law or agreed to in writing, software
19
- * distributed under the License is distributed on an "AS IS" BASIS,
20
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- * See the License for the specific language governing permissions and
22
- * limitations under the License.
23
- */
24
- // Node modules
25
1
  import { createServer } from 'node:http';
26
2
  import https from 'node:https';
27
3
  import os from 'node:os';
@@ -29,100 +5,28 @@ import path from 'node:path';
29
5
  import { existsSync, promises as fs, unlinkSync } from 'node:fs';
30
6
  import EventEmitter from 'node:events';
31
7
  import { appendFile } from 'node:fs/promises';
32
- // Third-party modules
33
8
  import express from 'express';
34
9
  import WebSocket, { WebSocketServer } from 'ws';
35
10
  import multer from 'multer';
36
- // AnsiLogger module
37
11
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
38
- // @matter
39
12
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
40
13
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
41
14
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
42
- // Matterbridge
43
15
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
44
16
  import { plg } from './matterbridgeTypes.js';
45
17
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
46
18
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
47
- /**
48
- * Websocket message ID for logging.
49
- *
50
- * @constant {number}
51
- */
52
19
  export const WS_ID_LOG = 0;
53
- /**
54
- * Websocket message ID indicating a refresh is needed.
55
- *
56
- * @constant {number}
57
- */
58
20
  export const WS_ID_REFRESH_NEEDED = 1;
59
- /**
60
- * Websocket message ID indicating a restart is needed.
61
- *
62
- * @constant {number}
63
- */
64
21
  export const WS_ID_RESTART_NEEDED = 2;
65
- /**
66
- * Websocket message ID indicating a cpu update.
67
- *
68
- * @constant {number}
69
- */
70
22
  export const WS_ID_CPU_UPDATE = 3;
71
- /**
72
- * Websocket message ID indicating a memory update.
73
- *
74
- * @constant {number}
75
- */
76
23
  export const WS_ID_MEMORY_UPDATE = 4;
77
- /**
78
- * Websocket message ID indicating an uptime update.
79
- *
80
- * @constant {number}
81
- */
82
24
  export const WS_ID_UPTIME_UPDATE = 5;
83
- /**
84
- * Websocket message ID indicating a snackbar message.
85
- *
86
- * @constant {number}
87
- */
88
25
  export const WS_ID_SNACKBAR = 6;
89
- /**
90
- * Websocket message ID indicating matterbridge has un update available.
91
- *
92
- * @constant {number}
93
- */
94
26
  export const WS_ID_UPDATE_NEEDED = 7;
95
- /**
96
- * Websocket message ID indicating a state update.
97
- *
98
- * @constant {number}
99
- */
100
27
  export const WS_ID_STATEUPDATE = 8;
101
- /**
102
- * Websocket message ID indicating to close a permanent snackbar message.
103
- *
104
- * @constant {number}
105
- */
106
28
  export const WS_ID_CLOSE_SNACKBAR = 9;
107
- /**
108
- * Websocket message ID indicating a shelly system update.
109
- * check:
110
- * curl -k http://127.0.0.1:8101/api/updates/sys/check
111
- * perform:
112
- * curl -k http://127.0.0.1:8101/api/updates/sys/perform
113
- *
114
- * @constant {number}
115
- */
116
29
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
117
- /**
118
- * Websocket message ID indicating a shelly main update.
119
- * check:
120
- * curl -k http://127.0.0.1:8101/api/updates/main/check
121
- * perform:
122
- * curl -k http://127.0.0.1:8101/api/updates/main/perform
123
- *
124
- * @constant {number}
125
- */
126
30
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
127
31
  export class Frontend extends EventEmitter {
128
32
  matterbridge;
@@ -135,7 +39,8 @@ export class Frontend extends EventEmitter {
135
39
  constructor(matterbridge) {
136
40
  super();
137
41
  this.matterbridge = matterbridge;
138
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
42
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
43
+ this.log.logNameColor = '\x1b[38;5;97m';
139
44
  }
140
45
  set logLevel(logLevel) {
141
46
  this.log.logLevel = logLevel;
@@ -143,39 +48,10 @@ export class Frontend extends EventEmitter {
143
48
  async start(port = 8283) {
144
49
  this.port = port;
145
50
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
146
- // Initialize multer with the upload directory
147
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
51
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
148
52
  const upload = multer({ dest: uploadDir });
149
- // Create the express app that serves the frontend
150
53
  this.expressApp = express();
151
- // Inject logging/debug wrapper for route/middleware registration
152
- /*
153
- const methods = ['get', 'post', 'put', 'delete', 'use'];
154
- for (const method of methods) {
155
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
- const original = (this.expressApp as any)[method].bind(this.expressApp);
157
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
159
- try {
160
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
161
- return original(path, ...rest);
162
- } catch (err) {
163
- console.error(`[ERROR] Failed to register route: ${path}`);
164
- throw err;
165
- }
166
- };
167
- }
168
- */
169
- // Log all requests to the server for debugging
170
- /*
171
- this.expressApp.use((req, res, next) => {
172
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
173
- next();
174
- });
175
- */
176
- // Serve static files from '/static' endpoint
177
54
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
178
- // Read the package.json file to get the frontend version
179
55
  try {
180
56
  this.log.debug(`Reading frontend package.json...`);
181
57
  const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
@@ -183,11 +59,9 @@ export class Frontend extends EventEmitter {
183
59
  this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
184
60
  }
185
61
  catch (error) {
186
- // istanbul ignore next
187
62
  this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
188
63
  }
189
64
  if (!hasParameter('ssl')) {
190
- // Create an HTTP server and attach the express app
191
65
  try {
192
66
  this.log.debug(`Creating HTTP server...`);
193
67
  this.httpServer = createServer(this.expressApp);
@@ -197,7 +71,6 @@ export class Frontend extends EventEmitter {
197
71
  this.emit('server_error', error);
198
72
  return;
199
73
  }
200
- // Listen on the specified port
201
74
  if (hasParameter('ingress')) {
202
75
  this.httpServer.listen(this.port, '0.0.0.0', () => {
203
76
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -236,7 +109,6 @@ export class Frontend extends EventEmitter {
236
109
  let passphrase;
237
110
  let httpsServerOptions = {};
238
111
  if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
239
- // Load the p12 certificate and the passphrase
240
112
  try {
241
113
  pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
242
114
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -248,7 +120,7 @@ export class Frontend extends EventEmitter {
248
120
  }
249
121
  try {
250
122
  passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
251
- passphrase = passphrase.trim(); // Ensure no extra characters
123
+ passphrase = passphrase.trim();
252
124
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
253
125
  }
254
126
  catch (error) {
@@ -259,7 +131,6 @@ export class Frontend extends EventEmitter {
259
131
  httpsServerOptions = { pfx, passphrase };
260
132
  }
261
133
  else {
262
- // 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.
263
134
  try {
264
135
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
265
136
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -289,10 +160,9 @@ export class Frontend extends EventEmitter {
289
160
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
290
161
  }
291
162
  if (hasParameter('mtls')) {
292
- httpsServerOptions.requestCert = true; // Request client certificate
293
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
163
+ httpsServerOptions.requestCert = true;
164
+ httpsServerOptions.rejectUnauthorized = true;
294
165
  }
295
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
296
166
  try {
297
167
  this.log.debug(`Creating HTTPS server...`);
298
168
  this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
@@ -302,7 +172,6 @@ export class Frontend extends EventEmitter {
302
172
  this.emit('server_error', error);
303
173
  return;
304
174
  }
305
- // Listen on the specified port
306
175
  if (hasParameter('ingress')) {
307
176
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
308
177
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -332,19 +201,17 @@ export class Frontend extends EventEmitter {
332
201
  return;
333
202
  });
334
203
  }
335
- // Create a WebSocket server and attach it to the http or https server
336
204
  const wssPort = this.port;
337
205
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
338
206
  this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
339
207
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
340
208
  this.webSocketServer.on('connection', (ws, request) => {
341
209
  const clientIp = request.socket.remoteAddress;
342
- // Set the global logger callback for the WebSocketServer
343
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
344
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
345
- callbackLogLevel = "info" /* LogLevel.INFO */;
346
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
347
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
210
+ let callbackLogLevel = "notice";
211
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
212
+ callbackLogLevel = "info";
213
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
214
+ callbackLogLevel = "debug";
348
215
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
349
216
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
350
217
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -366,7 +233,6 @@ export class Frontend extends EventEmitter {
366
233
  }
367
234
  });
368
235
  ws.on('error', (error) => {
369
- // istanbul ignore next
370
236
  this.log.error(`WebSocket client error: ${error}`);
371
237
  });
372
238
  });
@@ -380,7 +246,6 @@ export class Frontend extends EventEmitter {
380
246
  this.webSocketServer.on('error', (ws, error) => {
381
247
  this.log.error(`WebSocketServer error: ${error}`);
382
248
  });
383
- // Subscribe to cli events
384
249
  cliEmitter.removeAllListeners();
385
250
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
386
251
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -391,8 +256,6 @@ export class Frontend extends EventEmitter {
391
256
  cliEmitter.on('cpu', (cpuUsage) => {
392
257
  this.wssSendCpuUpdate(cpuUsage);
393
258
  });
394
- // Endpoint to validate login code
395
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
396
259
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
397
260
  const { password } = req.body;
398
261
  this.log.debug('The frontend sent /api/login', password);
@@ -411,27 +274,23 @@ export class Frontend extends EventEmitter {
411
274
  this.log.warn('/api/login error wrong password');
412
275
  res.json({ valid: false });
413
276
  }
414
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
415
277
  }
416
278
  catch (error) {
417
279
  this.log.error('/api/login error getting password');
418
280
  res.json({ valid: false });
419
281
  }
420
282
  });
421
- // Endpoint to provide health check for docker
422
283
  this.expressApp.get('/health', (req, res) => {
423
284
  this.log.debug('Express received /health');
424
285
  const healthStatus = {
425
- status: 'ok', // Indicate service is healthy
426
- uptime: process.uptime(), // Server uptime in seconds
427
- timestamp: new Date().toISOString(), // Current timestamp
286
+ status: 'ok',
287
+ uptime: process.uptime(),
288
+ timestamp: new Date().toISOString(),
428
289
  };
429
290
  res.status(200).json(healthStatus);
430
291
  });
431
- // Endpoint to provide memory usage details
432
292
  this.expressApp.get('/memory', async (req, res) => {
433
293
  this.log.debug('Express received /memory');
434
- // Memory usage from process
435
294
  const memoryUsageRaw = process.memoryUsage();
436
295
  const memoryUsage = {
437
296
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -440,13 +299,10 @@ export class Frontend extends EventEmitter {
440
299
  external: this.formatMemoryUsage(memoryUsageRaw.external),
441
300
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
442
301
  };
443
- // V8 heap statistics
444
302
  const { default: v8 } = await import('node:v8');
445
303
  const heapStatsRaw = v8.getHeapStatistics();
446
304
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
447
- // Format heapStats
448
305
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
449
- // Format heapSpaces
450
306
  const heapSpaces = heapSpacesRaw.map((space) => ({
451
307
  ...space,
452
308
  space_size: this.formatMemoryUsage(space.space_size),
@@ -464,23 +320,19 @@ export class Frontend extends EventEmitter {
464
320
  };
465
321
  res.status(200).json(memoryReport);
466
322
  });
467
- // Endpoint to provide settings
468
323
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
469
324
  this.log.debug('The frontend sent /api/settings');
470
325
  res.json(await this.getApiSettings());
471
326
  });
472
- // Endpoint to provide plugins
473
327
  this.expressApp.get('/api/plugins', async (req, res) => {
474
328
  this.log.debug('The frontend sent /api/plugins');
475
329
  res.json(this.getPlugins());
476
330
  });
477
- // Endpoint to provide devices
478
331
  this.expressApp.get('/api/devices', async (req, res) => {
479
332
  this.log.debug('The frontend sent /api/devices');
480
333
  const devices = await this.getDevices();
481
334
  res.json(devices);
482
335
  });
483
- // Endpoint to view the matterbridge log
484
336
  this.expressApp.get('/api/view-mblog', async (req, res) => {
485
337
  this.log.debug('The frontend sent /api/view-mblog');
486
338
  try {
@@ -493,7 +345,6 @@ export class Frontend extends EventEmitter {
493
345
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
494
346
  }
495
347
  });
496
- // Endpoint to view the matter.js log
497
348
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
498
349
  this.log.debug('The frontend sent /api/view-mjlog');
499
350
  try {
@@ -506,11 +357,9 @@ export class Frontend extends EventEmitter {
506
357
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
507
358
  }
508
359
  });
509
- // Endpoint to view the diagnostic.log
510
360
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
511
361
  this.log.debug('The frontend sent /api/view-diagnostic');
512
362
  const serverNodes = [];
513
- // istanbul ignore else
514
363
  if (this.matterbridge.bridgeMode === 'bridge') {
515
364
  if (this.matterbridge.serverNode)
516
365
  serverNodes.push(this.matterbridge.serverNode);
@@ -521,7 +370,6 @@ export class Frontend extends EventEmitter {
521
370
  serverNodes.push(plugin.serverNode);
522
371
  }
523
372
  }
524
- // istanbul ignore next
525
373
  for (const device of this.matterbridge.getDevices()) {
526
374
  if (device.serverNode)
527
375
  serverNodes.push(device.serverNode);
@@ -544,20 +392,17 @@ export class Frontend extends EventEmitter {
544
392
  values: [...serverNodes],
545
393
  })));
546
394
  delete Logger.destinations.diagnostic;
547
- await wait(500); // Wait for the log to be written
395
+ await wait(500);
548
396
  try {
549
397
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
550
398
  res.type('text/plain');
551
399
  res.send(data.slice(29));
552
400
  }
553
401
  catch (error) {
554
- // istanbul ignore next
555
402
  this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
556
- // istanbul ignore next
557
403
  res.status(500).send('Error reading diagnostic log file.');
558
404
  }
559
405
  });
560
- // Endpoint to view the shelly log
561
406
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
562
407
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
563
408
  try {
@@ -570,7 +415,6 @@ export class Frontend extends EventEmitter {
570
415
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
571
416
  }
572
417
  });
573
- // Endpoint to download the matterbridge log
574
418
  this.expressApp.get('/api/download-mblog', async (req, res) => {
575
419
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
576
420
  try {
@@ -584,14 +428,12 @@ export class Frontend extends EventEmitter {
584
428
  }
585
429
  res.type('text/plain');
586
430
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
587
- /* istanbul ignore if */
588
431
  if (error) {
589
432
  this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
590
433
  res.status(500).send('Error downloading the matterbridge log file');
591
434
  }
592
435
  });
593
436
  });
594
- // Endpoint to download the matter log
595
437
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
596
438
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
597
439
  try {
@@ -605,14 +447,12 @@ export class Frontend extends EventEmitter {
605
447
  }
606
448
  res.type('text/plain');
607
449
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
608
- /* istanbul ignore if */
609
450
  if (error) {
610
451
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
611
452
  res.status(500).send('Error downloading the matter log file');
612
453
  }
613
454
  });
614
455
  });
615
- // Endpoint to download the shelly log
616
456
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
617
457
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
618
458
  try {
@@ -626,90 +466,74 @@ export class Frontend extends EventEmitter {
626
466
  }
627
467
  res.type('text/plain');
628
468
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
629
- /* istanbul ignore if */
630
469
  if (error) {
631
470
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
632
471
  res.status(500).send('Error downloading Shelly system log file');
633
472
  }
634
473
  });
635
474
  });
636
- // Endpoint to download the matterbridge storage directory
637
475
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
638
476
  this.log.debug('The frontend sent /api/download-mbstorage');
639
477
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
640
478
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
641
- /* istanbul ignore if */
642
479
  if (error) {
643
480
  this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
644
481
  res.status(500).send('Error downloading the matterbridge storage file');
645
482
  }
646
483
  });
647
484
  });
648
- // Endpoint to download the matter storage file
649
485
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
650
486
  this.log.debug('The frontend sent /api/download-mjstorage');
651
487
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
652
488
  res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
653
- /* istanbul ignore if */
654
489
  if (error) {
655
490
  this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
656
491
  res.status(500).send('Error downloading the matter storage zip file');
657
492
  }
658
493
  });
659
494
  });
660
- // Endpoint to download the matterbridge plugin directory
661
495
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
662
496
  this.log.debug('The frontend sent /api/download-pluginstorage');
663
497
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
664
498
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
665
- /* istanbul ignore if */
666
499
  if (error) {
667
500
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
668
501
  res.status(500).send('Error downloading the matterbridge plugin storage file');
669
502
  }
670
503
  });
671
504
  });
672
- // Endpoint to download the matterbridge plugin config files
673
505
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
674
506
  this.log.debug('The frontend sent /api/download-pluginconfig');
675
507
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
676
508
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
677
- /* istanbul ignore if */
678
509
  if (error) {
679
510
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
680
511
  res.status(500).send('Error downloading the matterbridge plugin config file');
681
512
  }
682
513
  });
683
514
  });
684
- // Endpoint to download the matterbridge backup (created with the backup command)
685
515
  this.expressApp.get('/api/download-backup', async (req, res) => {
686
516
  this.log.debug('The frontend sent /api/download-backup');
687
517
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
688
- /* istanbul ignore if */
689
518
  if (error) {
690
519
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
691
520
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
692
521
  }
693
522
  });
694
523
  });
695
- // Endpoint to upload a package
696
524
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
697
525
  const { filename } = req.body;
698
526
  const file = req.file;
699
- /* istanbul ignore if */
700
527
  if (!file || !filename) {
701
528
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
702
529
  res.status(400).send('Invalid request: file and filename are required');
703
530
  return;
704
531
  }
705
532
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
706
- // Define the path where the plugin file will be saved
707
533
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
708
534
  try {
709
- // Move the uploaded file to the specified path
710
535
  await fs.rename(file.path, filePath);
711
536
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
712
- // Install the plugin package
713
537
  if (filename.endsWith('.tgz')) {
714
538
  const { spawnCommand } = await import('./utils/spawn.js');
715
539
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
@@ -729,7 +553,6 @@ export class Frontend extends EventEmitter {
729
553
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
730
554
  }
731
555
  });
732
- // Fallback for routing (must be the last route)
733
556
  this.expressApp.use((req, res) => {
734
557
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
735
558
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -738,16 +561,13 @@ export class Frontend extends EventEmitter {
738
561
  }
739
562
  async stop() {
740
563
  this.log.debug('Stopping the frontend...');
741
- // Remove listeners from the express app
742
564
  if (this.expressApp) {
743
565
  this.expressApp.removeAllListeners();
744
566
  this.expressApp = undefined;
745
567
  this.log.debug('Frontend app closed successfully');
746
568
  }
747
- // Close the WebSocket server
748
569
  if (this.webSocketServer) {
749
570
  this.log.debug('Closing WebSocket server...');
750
- // Close all active connections
751
571
  this.webSocketServer.clients.forEach((client) => {
752
572
  if (client.readyState === WebSocket.OPEN) {
753
573
  client.close();
@@ -756,7 +576,6 @@ export class Frontend extends EventEmitter {
756
576
  await withTimeout(new Promise((resolve) => {
757
577
  this.webSocketServer?.close((error) => {
758
578
  if (error) {
759
- // istanbul ignore next
760
579
  this.log.error(`Error closing WebSocket server: ${error}`);
761
580
  }
762
581
  else {
@@ -769,13 +588,11 @@ export class Frontend extends EventEmitter {
769
588
  this.webSocketServer.removeAllListeners();
770
589
  this.webSocketServer = undefined;
771
590
  }
772
- // Close the http server
773
591
  if (this.httpServer) {
774
592
  this.log.debug('Closing http server...');
775
593
  await withTimeout(new Promise((resolve) => {
776
594
  this.httpServer?.close((error) => {
777
595
  if (error) {
778
- // istanbul ignore next
779
596
  this.log.error(`Error closing http server: ${error}`);
780
597
  }
781
598
  else {
@@ -784,18 +601,16 @@ export class Frontend extends EventEmitter {
784
601
  }
785
602
  resolve();
786
603
  });
787
- }), 10000, false);
604
+ }), 5000, false);
788
605
  this.httpServer.removeAllListeners();
789
606
  this.httpServer = undefined;
790
607
  this.log.debug('Frontend http server closed successfully');
791
608
  }
792
- // Close the https server
793
609
  if (this.httpsServer) {
794
610
  this.log.debug('Closing https server...');
795
611
  await withTimeout(new Promise((resolve) => {
796
612
  this.httpsServer?.close((error) => {
797
613
  if (error) {
798
- // istanbul ignore next
799
614
  this.log.error(`Error closing https server: ${error}`);
800
615
  }
801
616
  else {
@@ -804,14 +619,13 @@ export class Frontend extends EventEmitter {
804
619
  }
805
620
  resolve();
806
621
  });
807
- }), 10000, false);
622
+ }), 5000, false);
808
623
  this.httpsServer.removeAllListeners();
809
624
  this.httpsServer = undefined;
810
625
  this.log.debug('Frontend https server closed successfully');
811
626
  }
812
627
  this.log.debug('Frontend stopped successfully');
813
628
  }
814
- // Function to format bytes to KB, MB, or GB
815
629
  formatMemoryUsage = (bytes) => {
816
630
  if (bytes >= 1024 ** 3) {
817
631
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -823,7 +637,6 @@ export class Frontend extends EventEmitter {
823
637
  return `${(bytes / 1024).toFixed(2)} KB`;
824
638
  }
825
639
  };
826
- // Function to format system uptime with only the most significant unit
827
640
  formatOsUpTime = (seconds) => {
828
641
  if (seconds >= 86400) {
829
642
  const days = Math.floor(seconds / 86400);
@@ -839,13 +652,7 @@ export class Frontend extends EventEmitter {
839
652
  }
840
653
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
841
654
  };
842
- /**
843
- * Retrieves the api settings data.
844
- *
845
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
846
- */
847
655
  async getApiSettings() {
848
- // Update the system information
849
656
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
850
657
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
851
658
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -854,7 +661,6 @@ export class Frontend extends EventEmitter {
854
661
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
855
662
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
856
663
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
857
- // Update the matterbridge information
858
664
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
859
665
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
860
666
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
@@ -866,8 +672,8 @@ export class Frontend extends EventEmitter {
866
672
  this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
867
673
  this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
868
674
  this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
869
- // Update the matterbridge information in bridge mode
870
675
  if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
676
+ this.matterbridge.matterbridgeInformation.matter = this.matterbridge.getServerNodeData(this.matterbridge.serverNode);
871
677
  this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
872
678
  this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
873
679
  this.matterbridge.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.manualPairingCode;
@@ -876,12 +682,6 @@ export class Frontend extends EventEmitter {
876
682
  }
877
683
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
878
684
  }
879
- /**
880
- * Retrieves the reachable attribute.
881
- *
882
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
883
- * @returns {boolean} The reachable attribute.
884
- */
885
685
  getReachability(device) {
886
686
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
887
687
  return false;
@@ -893,12 +693,6 @@ export class Frontend extends EventEmitter {
893
693
  return true;
894
694
  return false;
895
695
  }
896
- /**
897
- * Retrieves the power source attribute.
898
- *
899
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
900
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
901
- */
902
696
  getPowerSource(endpoint) {
903
697
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
904
698
  return undefined;
@@ -914,33 +708,18 @@ export class Frontend extends EventEmitter {
914
708
  }
915
709
  return;
916
710
  };
917
- // Root endpoint
918
711
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
919
712
  return powerSource(endpoint);
920
- // Child endpoints
921
713
  for (const child of endpoint.getChildEndpoints()) {
922
714
  if (child.hasClusterServer(PowerSource.Cluster.id))
923
715
  return powerSource(child);
924
716
  }
925
717
  }
926
- /**
927
- * Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
928
- *
929
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
930
- * @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
931
- */
932
718
  getMatterDataFromDevice(device) {
933
719
  if (device.mode === 'server' && device.serverNode) {
934
720
  return this.matterbridge.getServerNodeData(device.serverNode);
935
721
  }
936
722
  }
937
- /**
938
- * Retrieves the cluster text description from a given device.
939
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
940
- *
941
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
942
- * @returns {string} The attributes description of the cluster servers in the device.
943
- */
944
723
  getClusterTextFromDevice(device) {
945
724
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
946
725
  return '';
@@ -951,7 +730,6 @@ export class Frontend extends EventEmitter {
951
730
  if (composed)
952
731
  return 'Composed: ' + composed.value;
953
732
  }
954
- // istanbul ignore next cause is not reachable
955
733
  return '';
956
734
  };
957
735
  const getFixedLabel = (device) => {
@@ -961,13 +739,11 @@ export class Frontend extends EventEmitter {
961
739
  if (composed)
962
740
  return 'Composed: ' + composed.value;
963
741
  }
964
- // istanbul ignore next cause is not reacheable
965
742
  return '';
966
743
  };
967
744
  let attributes = '';
968
745
  let supportedModes = [];
969
746
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
970
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
971
747
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
972
748
  return;
973
749
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1057,17 +833,11 @@ export class Frontend extends EventEmitter {
1057
833
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1058
834
  attributes += `${getUserLabel(device)} `;
1059
835
  });
1060
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1061
836
  return attributes.trimStart().trimEnd();
1062
837
  }
1063
- /**
1064
- * Retrieves the registered plugins sanitized for res.json().
1065
- *
1066
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1067
- */
1068
838
  getPlugins() {
1069
839
  if (this.matterbridge.hasCleanupStarted)
1070
- return []; // Skip if cleanup has started
840
+ return [];
1071
841
  const baseRegisteredPlugins = [];
1072
842
  for (const plugin of this.matterbridge.plugins) {
1073
843
  baseRegisteredPlugins.push({
@@ -1097,7 +867,7 @@ export class Frontend extends EventEmitter {
1097
867
  schemaJson: plugin.schemaJson,
1098
868
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1099
869
  hasBlackList: plugin.configJson?.blackList !== undefined,
1100
- // Childbridge mode specific data
870
+ matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1101
871
  paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
1102
872
  qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
1103
873
  manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
@@ -1107,21 +877,13 @@ export class Frontend extends EventEmitter {
1107
877
  }
1108
878
  return baseRegisteredPlugins;
1109
879
  }
1110
- /**
1111
- * Retrieves the devices from Matterbridge.
1112
- *
1113
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1114
- * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1115
- */
1116
880
  async getDevices(pluginName) {
1117
881
  if (this.matterbridge.hasCleanupStarted)
1118
- return []; // Skip if cleanup has started
882
+ return [];
1119
883
  const devices = [];
1120
884
  for (const device of this.matterbridge.devices.array()) {
1121
- // Filter by pluginName if provided
1122
885
  if (pluginName && pluginName !== device.plugin)
1123
886
  continue;
1124
- // Check if the device has the required properties
1125
887
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1126
888
  continue;
1127
889
  devices.push({
@@ -1141,37 +903,22 @@ export class Frontend extends EventEmitter {
1141
903
  }
1142
904
  return devices;
1143
905
  }
1144
- /**
1145
- * Retrieves the clusters from a given plugin and endpoint number.
1146
- *
1147
- * Response for /api/clusters
1148
- *
1149
- * @param {string} pluginName - The name of the plugin.
1150
- * @param {number} endpointNumber - The endpoint number.
1151
- * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1152
- */
1153
906
  getClusters(pluginName, endpointNumber) {
1154
907
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1155
908
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
1156
909
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1157
910
  return;
1158
911
  }
1159
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1160
- // Get the device types from the main endpoint
1161
912
  const deviceTypes = [];
1162
913
  const clusters = [];
1163
914
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1164
915
  deviceTypes.push(d.deviceType);
1165
916
  });
1166
- // Get the clusters from the main endpoint
1167
917
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1168
918
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1169
919
  return;
1170
920
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1171
921
  return;
1172
- // console.log(
1173
- // `${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}`,
1174
- // );
1175
922
  clusters.push({
1176
923
  endpoint: endpoint.number.toString(),
1177
924
  id: 'main',
@@ -1184,19 +931,12 @@ export class Frontend extends EventEmitter {
1184
931
  attributeLocalValue: attributeValue,
1185
932
  });
1186
933
  });
1187
- // Get the child endpoints
1188
934
  const childEndpoints = endpoint.getChildEndpoints();
1189
- // if (childEndpoints.length === 0) {
1190
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1191
- // }
1192
935
  childEndpoints.forEach((childEndpoint) => {
1193
- // istanbul ignore if cause is not reachable: should never happen but ...
1194
936
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1195
937
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1196
938
  return;
1197
939
  }
1198
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1199
- // Get the device types of the child endpoint
1200
940
  const deviceTypes = [];
1201
941
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1202
942
  deviceTypes.push(d.deviceType);
@@ -1206,12 +946,9 @@ export class Frontend extends EventEmitter {
1206
946
  return;
1207
947
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1208
948
  return;
1209
- // console.log(
1210
- // `${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}`,
1211
- // );
1212
949
  clusters.push({
1213
950
  endpoint: childEndpoint.number.toString(),
1214
- id: childEndpoint.maybeId ?? 'null', // Never happens
951
+ id: childEndpoint.maybeId ?? 'null',
1215
952
  deviceTypes,
1216
953
  clusterName: capitalizeFirstLetter(clusterName),
1217
954
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -1224,13 +961,6 @@ export class Frontend extends EventEmitter {
1224
961
  });
1225
962
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
1226
963
  }
1227
- /**
1228
- * Handles incoming websocket messages for the Matterbridge frontend.
1229
- *
1230
- * @param {WebSocket} client - The websocket client that sent the message.
1231
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1232
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1233
- */
1234
964
  async wsMessageHandler(client, message) {
1235
965
  let data;
1236
966
  try {
@@ -1277,48 +1007,35 @@ export class Frontend extends EventEmitter {
1277
1007
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
1278
1008
  const packageName = data.params.packageName.replace(/@.*$/, '');
1279
1009
  if (data.params.restart === false && packageName !== 'matterbridge') {
1280
- // The install comes from InstallPlugins
1281
1010
  this.matterbridge.plugins
1282
1011
  .add(packageName)
1283
1012
  .then((plugin) => {
1284
- // istanbul ignore next if
1285
1013
  if (plugin) {
1286
- // The plugin is not registered
1287
1014
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1288
- // In childbridge mode the plugins server node is not started when added
1289
1015
  if (this.matterbridge.bridgeMode === 'childbridge')
1290
1016
  this.wssSendRestartRequired(true, true);
1291
1017
  this.matterbridge.plugins
1292
1018
  .load(plugin, true, 'The plugin has been added', true)
1293
- // eslint-disable-next-line promise/no-nesting
1294
1019
  .then(() => {
1295
1020
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
1296
1021
  this.wssSendRefreshRequired('plugins');
1297
1022
  return;
1298
1023
  })
1299
- // eslint-disable-next-line promise/no-nesting
1300
1024
  .catch((_error) => {
1301
- //
1302
1025
  });
1303
1026
  }
1304
1027
  else {
1305
- // The plugin is already registered
1306
1028
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1307
1029
  this.wssSendRefreshRequired('plugins');
1308
1030
  this.wssSendRestartRequired(true, true);
1309
1031
  }
1310
1032
  return;
1311
1033
  })
1312
- // eslint-disable-next-line promise/no-nesting
1313
1034
  .catch((_error) => {
1314
- //
1315
1035
  });
1316
1036
  }
1317
1037
  else {
1318
- // The package is matterbridge
1319
- // istanbul ignore next
1320
1038
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1321
- // istanbul ignore next if
1322
1039
  if (this.matterbridge.restartMode !== '') {
1323
1040
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1324
1041
  this.matterbridge.shutdownProcess();
@@ -1340,9 +1057,7 @@ export class Frontend extends EventEmitter {
1340
1057
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1341
1058
  return;
1342
1059
  }
1343
- // The package is a plugin
1344
1060
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1345
- // istanbul ignore next if
1346
1061
  if (plugin) {
1347
1062
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1348
1063
  await this.matterbridge.plugins.remove(data.params.packageName);
@@ -1350,7 +1065,6 @@ export class Frontend extends EventEmitter {
1350
1065
  this.wssSendRefreshRequired('plugins');
1351
1066
  this.wssSendRefreshRequired('devices');
1352
1067
  }
1353
- // Uninstall the package
1354
1068
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1355
1069
  const { spawnCommand } = await import('./utils/spawn.js');
1356
1070
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1391,7 +1105,6 @@ export class Frontend extends EventEmitter {
1391
1105
  return;
1392
1106
  })
1393
1107
  .catch((_error) => {
1394
- //
1395
1108
  });
1396
1109
  }
1397
1110
  else {
@@ -1438,7 +1151,6 @@ export class Frontend extends EventEmitter {
1438
1151
  return;
1439
1152
  })
1440
1153
  .catch((_error) => {
1441
- //
1442
1154
  });
1443
1155
  }
1444
1156
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1464,7 +1176,6 @@ export class Frontend extends EventEmitter {
1464
1176
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1465
1177
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1466
1178
  if (plugin.serverNode) {
1467
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1468
1179
  await this.matterbridge.stopServerNode(plugin.serverNode);
1469
1180
  plugin.serverNode = undefined;
1470
1181
  }
@@ -1474,17 +1185,19 @@ export class Frontend extends EventEmitter {
1474
1185
  this.matterbridge.devices.remove(device);
1475
1186
  }
1476
1187
  }
1188
+ if (plugin.type === 'DynamicPlatform' && !plugin.locked) {
1189
+ await this.matterbridge.createDynamicPlugin(plugin);
1190
+ }
1477
1191
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1478
- plugin.restartRequired = false; // Reset plugin restartRequired
1192
+ plugin.restartRequired = false;
1479
1193
  let needRestart = 0;
1480
1194
  for (const plugin of this.matterbridge.plugins) {
1481
1195
  if (plugin.restartRequired)
1482
1196
  needRestart++;
1483
1197
  }
1484
1198
  if (needRestart === 0) {
1485
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1199
+ this.wssSendRestartNotRequired(true);
1486
1200
  }
1487
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1488
1201
  if (plugin.serverNode)
1489
1202
  await this.matterbridge.startServerNode(plugin.serverNode);
1490
1203
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1620,7 +1333,7 @@ export class Frontend extends EventEmitter {
1620
1333
  this.log.debug(`*Sending data for node ${data.params.id}`);
1621
1334
  this.wssSendRefreshRequired('matter', { matter: { ...this.matterbridge.getServerNodeData(serverNode) } });
1622
1335
  }
1623
- if (data.params.commission) {
1336
+ if (data.params.startCommission) {
1624
1337
  await serverNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
1625
1338
  this.matterbridge.advertisingNodes.set(serverNode.id, Date.now());
1626
1339
  this.log.debug(`*Commissioning has been sent for node ${data.params.id}`);
@@ -1756,22 +1469,22 @@ export class Frontend extends EventEmitter {
1756
1469
  if (isValidString(data.params.value, 4)) {
1757
1470
  this.log.debug('Matterbridge logger level:', data.params.value);
1758
1471
  if (data.params.value === 'Debug') {
1759
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1472
+ await this.matterbridge.setLogLevel("debug");
1760
1473
  }
1761
1474
  else if (data.params.value === 'Info') {
1762
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1475
+ await this.matterbridge.setLogLevel("info");
1763
1476
  }
1764
1477
  else if (data.params.value === 'Notice') {
1765
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1478
+ await this.matterbridge.setLogLevel("notice");
1766
1479
  }
1767
1480
  else if (data.params.value === 'Warn') {
1768
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1481
+ await this.matterbridge.setLogLevel("warn");
1769
1482
  }
1770
1483
  else if (data.params.value === 'Error') {
1771
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1484
+ await this.matterbridge.setLogLevel("error");
1772
1485
  }
1773
1486
  else if (data.params.value === 'Fatal') {
1774
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1487
+ await this.matterbridge.setLogLevel("fatal");
1775
1488
  }
1776
1489
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1777
1490
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1782,7 +1495,6 @@ export class Frontend extends EventEmitter {
1782
1495
  this.log.debug('Matterbridge file log:', data.params.value);
1783
1496
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1784
1497
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1785
- // Create the file logger for matterbridge
1786
1498
  if (data.params.value)
1787
1499
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1788
1500
  else
@@ -1936,19 +1648,15 @@ export class Frontend extends EventEmitter {
1936
1648
  return;
1937
1649
  }
1938
1650
  const config = plugin.configJson;
1939
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1940
1651
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1941
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1942
1652
  if (select === 'serial')
1943
1653
  this.log.info(`Selected device serial ${data.params.serial}`);
1944
1654
  if (select === 'name')
1945
1655
  this.log.info(`Selected device name ${data.params.name}`);
1946
1656
  if (config && select && (select === 'serial' || select === 'name')) {
1947
- // Remove postfix from the serial if it exists
1948
1657
  if (config.postfix) {
1949
1658
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1950
1659
  }
1951
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1952
1660
  if (isValidArray(config.whiteList, 1)) {
1953
1661
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1954
1662
  config.whiteList.push(data.params.serial);
@@ -1957,7 +1665,6 @@ export class Frontend extends EventEmitter {
1957
1665
  config.whiteList.push(data.params.name);
1958
1666
  }
1959
1667
  }
1960
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1961
1668
  if (isValidArray(config.blackList, 1)) {
1962
1669
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1963
1670
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1988,9 +1695,7 @@ export class Frontend extends EventEmitter {
1988
1695
  return;
1989
1696
  }
1990
1697
  const config = plugin.configJson;
1991
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1992
1698
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1993
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1994
1699
  if (select === 'serial')
1995
1700
  this.log.info(`Unselected device serial ${data.params.serial}`);
1996
1701
  if (select === 'name')
@@ -1999,7 +1704,6 @@ export class Frontend extends EventEmitter {
1999
1704
  if (config.postfix) {
2000
1705
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
2001
1706
  }
2002
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
2003
1707
  if (isValidArray(config.whiteList, 1)) {
2004
1708
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
2005
1709
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -2008,7 +1712,6 @@ export class Frontend extends EventEmitter {
2008
1712
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
2009
1713
  }
2010
1714
  }
2011
- // Add the serial to the blackList
2012
1715
  if (isValidArray(config.blackList)) {
2013
1716
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
2014
1717
  config.blackList.push(data.params.serial);
@@ -2042,253 +1745,126 @@ export class Frontend extends EventEmitter {
2042
1745
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
2043
1746
  }
2044
1747
  }
2045
- /**
2046
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
2047
- *
2048
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2049
- * @param {string} time - The time string of the message
2050
- * @param {string} name - The logger name of the message
2051
- * @param {string} message - The content of the message.
2052
- *
2053
- * @remarks
2054
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
2055
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
2056
- * The function sends the message to all connected clients.
2057
- */
2058
1748
  wssSendMessage(level, time, name, message) {
2059
1749
  if (!level || !time || !name || !message)
2060
1750
  return;
2061
- // Remove ANSI escape codes from the message
2062
- // eslint-disable-next-line no-control-regex
2063
1751
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2064
- // Remove leading asterisks from the message
2065
1752
  message = message.replace(/^\*+/, '');
2066
- // Replace all occurrences of \t and \n
2067
1753
  message = message.replace(/[\t\n]/g, '');
2068
- // Remove non-printable characters
2069
- // eslint-disable-next-line no-control-regex
2070
1754
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2071
- // Replace all occurrences of \" with "
2072
1755
  message = message.replace(/\\"/g, '"');
2073
- // Replace all occurrences of angle-brackets with &lt; and &gt;"
2074
1756
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2075
- // Define the maximum allowed length for continuous characters without a space
2076
1757
  const maxContinuousLength = 100;
2077
1758
  const keepStartLength = 20;
2078
1759
  const keepEndLength = 20;
2079
- // Split the message into words
2080
1760
  message = message
2081
1761
  .split(' ')
2082
1762
  .map((word) => {
2083
- // If the word length exceeds the max continuous length, insert spaces and truncate
2084
1763
  if (word.length > maxContinuousLength) {
2085
1764
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2086
1765
  }
2087
1766
  return word;
2088
1767
  })
2089
1768
  .join(' ');
2090
- // Send the message to all connected clients
2091
1769
  this.webSocketServer?.clients.forEach((client) => {
2092
1770
  if (client.readyState === WebSocket.OPEN) {
2093
1771
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2094
1772
  }
2095
1773
  });
2096
1774
  }
2097
- /**
2098
- * Sends a need to refresh WebSocket message to all connected clients.
2099
- *
2100
- * @param {string} changed - The changed value. If null, the whole page will be refreshed.
2101
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2102
- * possible values for changed:
2103
- * - 'matterbridgeLatestVersion'
2104
- * - 'matterbridgeDevVersion'
2105
- * - 'matterbridgeAdvertise'
2106
- * - 'online'
2107
- * - 'offline'
2108
- * - 'reachability'
2109
- * - 'settings'
2110
- * - 'plugins'
2111
- * - 'pluginsRestart'
2112
- * - 'devices'
2113
- * - 'fabrics'
2114
- * - 'sessions'
2115
- * - 'matter' with param 'matter' (QRDivDevice)
2116
- */
2117
1775
  wssSendRefreshRequired(changed = null, params = {}) {
2118
1776
  this.log.debug('Sending a refresh required message to all connected clients');
2119
- // Send the message to all connected clients
2120
1777
  this.webSocketServer?.clients.forEach((client) => {
2121
1778
  if (client.readyState === WebSocket.OPEN) {
2122
1779
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed, ...params } }));
2123
1780
  }
2124
1781
  });
2125
1782
  }
2126
- /**
2127
- * Sends a need to restart WebSocket message to all connected clients.
2128
- *
2129
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2130
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2131
- */
2132
1783
  wssSendRestartRequired(snackbar = true, fixed = false) {
2133
1784
  this.log.debug('Sending a restart required message to all connected clients');
2134
1785
  this.matterbridge.matterbridgeInformation.restartRequired = true;
2135
1786
  this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
2136
1787
  if (snackbar === true)
2137
1788
  this.wssSendSnackbarMessage(`Restart required`, 0);
2138
- // Send the message to all connected clients
2139
1789
  this.webSocketServer?.clients.forEach((client) => {
2140
1790
  if (client.readyState === WebSocket.OPEN) {
2141
1791
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
2142
1792
  }
2143
1793
  });
2144
1794
  }
2145
- /**
2146
- * Sends a no need to restart WebSocket message to all connected clients.
2147
- *
2148
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
2149
- */
2150
1795
  wssSendRestartNotRequired(snackbar = true) {
2151
1796
  this.log.debug('Sending a restart not required message to all connected clients');
2152
1797
  this.matterbridge.matterbridgeInformation.restartRequired = false;
2153
1798
  if (snackbar === true)
2154
1799
  this.wssSendCloseSnackbarMessage(`Restart required`);
2155
- // Send the message to all connected clients
2156
1800
  this.webSocketServer?.clients.forEach((client) => {
2157
1801
  if (client.readyState === WebSocket.OPEN) {
2158
1802
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
2159
1803
  }
2160
1804
  });
2161
1805
  }
2162
- /**
2163
- * Sends a need to update WebSocket message to all connected clients.
2164
- *
2165
- * @param {boolean} devVersion - If true, the update is for a development version.
2166
- */
2167
1806
  wssSendUpdateRequired(devVersion = false) {
2168
1807
  this.log.debug('Sending an update required message to all connected clients');
2169
1808
  this.matterbridge.matterbridgeInformation.updateRequired = true;
2170
- // Send the message to all connected clients
2171
1809
  this.webSocketServer?.clients.forEach((client) => {
2172
1810
  if (client.readyState === WebSocket.OPEN) {
2173
1811
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
2174
1812
  }
2175
1813
  });
2176
1814
  }
2177
- /**
2178
- * Sends a cpu update message to all connected clients.
2179
- *
2180
- * @param {number} cpuUsage - The CPU usage percentage to send.
2181
- */
2182
1815
  wssSendCpuUpdate(cpuUsage) {
2183
1816
  if (hasParameter('debug'))
2184
1817
  this.log.debug('Sending a cpu update message to all connected clients');
2185
- // Send the message to all connected clients
2186
1818
  this.webSocketServer?.clients.forEach((client) => {
2187
1819
  if (client.readyState === WebSocket.OPEN) {
2188
1820
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
2189
1821
  }
2190
1822
  });
2191
1823
  }
2192
- /**
2193
- * Sends a memory update message to all connected clients.
2194
- *
2195
- * @param {string} totalMemory - The total memory in bytes.
2196
- * @param {string} freeMemory - The free memory in bytes.
2197
- * @param {string} rss - The resident set size in bytes.
2198
- * @param {string} heapTotal - The total heap memory in bytes.
2199
- * @param {string} heapUsed - The used heap memory in bytes.
2200
- * @param {string} external - The external memory in bytes.
2201
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2202
- */
2203
1824
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2204
1825
  if (hasParameter('debug'))
2205
1826
  this.log.debug('Sending a memory update message to all connected clients');
2206
- // Send the message to all connected clients
2207
1827
  this.webSocketServer?.clients.forEach((client) => {
2208
1828
  if (client.readyState === WebSocket.OPEN) {
2209
1829
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
2210
1830
  }
2211
1831
  });
2212
1832
  }
2213
- /**
2214
- * Sends an uptime update message to all connected clients.
2215
- *
2216
- * @param {string} systemUptime - The system uptime in a human-readable format.
2217
- * @param {string} processUptime - The process uptime in a human-readable format.
2218
- */
2219
1833
  wssSendUptimeUpdate(systemUptime, processUptime) {
2220
1834
  if (hasParameter('debug'))
2221
1835
  this.log.debug('Sending a uptime update message to all connected clients');
2222
- // Send the message to all connected clients
2223
1836
  this.webSocketServer?.clients.forEach((client) => {
2224
1837
  if (client.readyState === WebSocket.OPEN) {
2225
1838
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
2226
1839
  }
2227
1840
  });
2228
1841
  }
2229
- /**
2230
- * Sends an open snackbar message to all connected clients.
2231
- *
2232
- * @param {string} message - The message to send.
2233
- * @param {number} timeout - The timeout in seconds for the snackbar message.
2234
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2235
- */
2236
1842
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2237
1843
  this.log.debug('Sending a snackbar message to all connected clients');
2238
- // Send the message to all connected clients
2239
1844
  this.webSocketServer?.clients.forEach((client) => {
2240
1845
  if (client.readyState === WebSocket.OPEN) {
2241
1846
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
2242
1847
  }
2243
1848
  });
2244
1849
  }
2245
- /**
2246
- * Sends a close snackbar message to all connected clients.
2247
- *
2248
- * @param {string} message - The message to send.
2249
- */
2250
1850
  wssSendCloseSnackbarMessage(message) {
2251
1851
  this.log.debug('Sending a close snackbar message to all connected clients');
2252
- // Send the message to all connected clients
2253
1852
  this.webSocketServer?.clients.forEach((client) => {
2254
1853
  if (client.readyState === WebSocket.OPEN) {
2255
1854
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
2256
1855
  }
2257
1856
  });
2258
1857
  }
2259
- /**
2260
- * Sends an attribute update message to all connected WebSocket clients.
2261
- *
2262
- * @param {string | undefined} plugin - The name of the plugin.
2263
- * @param {string | undefined} serialNumber - The serial number of the device.
2264
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2265
- * @param {string} cluster - The cluster name where the attribute belongs.
2266
- * @param {string} attribute - The name of the attribute that changed.
2267
- * @param {number | string | boolean} value - The new value of the attribute.
2268
- *
2269
- * @remarks
2270
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2271
- * with the updated attribute information.
2272
- */
2273
1858
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
2274
1859
  this.log.debug('Sending an attribute update message to all connected clients');
2275
- // Send the message to all connected clients
2276
1860
  this.webSocketServer?.clients.forEach((client) => {
2277
1861
  if (client.readyState === WebSocket.OPEN) {
2278
1862
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
2279
1863
  }
2280
1864
  });
2281
1865
  }
2282
- /**
2283
- * Sends a message to all connected clients.
2284
- *
2285
- * @param {number} id - The message id.
2286
- * @param {string} method - The message method.
2287
- * @param {Record<string, string | number | boolean>} params - The message parameters.
2288
- */
2289
1866
  wssBroadcastMessage(id, method, params) {
2290
1867
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2291
- // Send the message to all connected clients
2292
1868
  this.webSocketServer?.clients.forEach((client) => {
2293
1869
  if (client.readyState === WebSocket.OPEN) {
2294
1870
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -2296,4 +1872,3 @@ export class Frontend extends EventEmitter {
2296
1872
  });
2297
1873
  }
2298
1874
  }
2299
- //# sourceMappingURL=frontend.js.map