matterbridge 3.2.7-dev-20250913-9d0d095 → 3.2.7

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