matterbridge 3.3.0 → 3.3.1-dev-20251007-4e5eaac

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 (289) hide show
  1. package/CHANGELOG.md +29 -3
  2. package/README.md +9 -1
  3. package/dist/broadcastServer.js +73 -0
  4. package/dist/broadcastServerTypes.js +1 -0
  5. package/dist/cli.js +2 -91
  6. package/dist/cliEmitter.js +0 -30
  7. package/dist/clusters/export.js +0 -2
  8. package/dist/defaultConfigSchema.js +0 -24
  9. package/dist/deviceManager.js +44 -98
  10. package/dist/devices/airConditioner.js +0 -57
  11. package/dist/devices/batteryStorage.js +1 -48
  12. package/dist/devices/cooktop.js +0 -55
  13. package/dist/devices/dishwasher.js +0 -57
  14. package/dist/devices/evse.js +10 -74
  15. package/dist/devices/export.js +0 -5
  16. package/dist/devices/extractorHood.js +0 -42
  17. package/dist/devices/heatPump.js +2 -50
  18. package/dist/devices/laundryDryer.js +3 -62
  19. package/dist/devices/laundryWasher.js +4 -70
  20. package/dist/devices/microwaveOven.js +5 -88
  21. package/dist/devices/oven.js +0 -85
  22. package/dist/devices/refrigerator.js +0 -102
  23. package/dist/devices/roboticVacuumCleaner.js +9 -100
  24. package/dist/devices/solarPower.js +0 -38
  25. package/dist/devices/speaker.js +0 -84
  26. package/dist/devices/temperatureControl.js +3 -25
  27. package/dist/devices/waterHeater.js +2 -82
  28. package/dist/dgram/coap.js +13 -126
  29. package/dist/dgram/dgram.js +2 -114
  30. package/dist/dgram/mb_coap.js +3 -41
  31. package/dist/dgram/mb_mdns.js +15 -80
  32. package/dist/dgram/mdns.js +137 -299
  33. package/dist/dgram/multicast.js +1 -62
  34. package/dist/dgram/unicast.js +0 -54
  35. package/dist/frontend.js +172 -604
  36. package/dist/frontendTypes.js +0 -45
  37. package/dist/helpers.js +4 -57
  38. package/dist/index.js +0 -41
  39. package/dist/logger/export.js +0 -1
  40. package/dist/matter/behaviors.js +0 -2
  41. package/dist/matter/clusters.js +0 -2
  42. package/dist/matter/devices.js +0 -2
  43. package/dist/matter/endpoints.js +0 -2
  44. package/dist/matter/export.js +0 -3
  45. package/dist/matter/types.js +0 -3
  46. package/dist/matterbridge.js +148 -877
  47. package/dist/matterbridgeAccessoryPlatform.js +0 -36
  48. package/dist/matterbridgeBehaviors.js +5 -65
  49. package/dist/matterbridgeDeviceTypes.js +17 -630
  50. package/dist/matterbridgeDynamicPlatform.js +0 -36
  51. package/dist/matterbridgeEndpoint.js +58 -1398
  52. package/dist/matterbridgeEndpointHelpers.js +12 -345
  53. package/dist/matterbridgePlatform.js +2 -341
  54. package/dist/matterbridgeTypes.js +0 -26
  55. package/dist/pluginManager.js +133 -254
  56. package/dist/shelly.js +11 -172
  57. package/dist/storage/export.js +0 -1
  58. package/dist/update.js +0 -71
  59. package/dist/utils/colorUtils.js +2 -97
  60. package/dist/utils/commandLine.js +0 -54
  61. package/dist/utils/copyDirectory.js +1 -38
  62. package/dist/utils/createDirectory.js +0 -33
  63. package/dist/utils/createZip.js +2 -47
  64. package/dist/utils/deepCopy.js +0 -39
  65. package/dist/utils/deepEqual.js +1 -72
  66. package/dist/utils/error.js +0 -41
  67. package/dist/utils/export.js +0 -1
  68. package/dist/utils/hex.js +0 -124
  69. package/dist/utils/isvalid.js +0 -101
  70. package/dist/utils/jestHelpers.js +3 -153
  71. package/dist/utils/network.js +76 -129
  72. package/dist/utils/spawn.js +5 -75
  73. package/dist/utils/wait.js +8 -60
  74. package/frontend/build/assets/index.js +4 -7
  75. package/frontend/build/assets/vendor_mui.js +1 -1
  76. package/frontend/package.json +1 -1
  77. package/npm-shrinkwrap.json +44 -44
  78. package/package.json +2 -3
  79. package/dist/cli.d.ts +0 -26
  80. package/dist/cli.d.ts.map +0 -1
  81. package/dist/cli.js.map +0 -1
  82. package/dist/cliEmitter.d.ts +0 -34
  83. package/dist/cliEmitter.d.ts.map +0 -1
  84. package/dist/cliEmitter.js.map +0 -1
  85. package/dist/clusters/export.d.ts +0 -2
  86. package/dist/clusters/export.d.ts.map +0 -1
  87. package/dist/clusters/export.js.map +0 -1
  88. package/dist/defaultConfigSchema.d.ts +0 -28
  89. package/dist/defaultConfigSchema.d.ts.map +0 -1
  90. package/dist/defaultConfigSchema.js.map +0 -1
  91. package/dist/deviceManager.d.ts +0 -112
  92. package/dist/deviceManager.d.ts.map +0 -1
  93. package/dist/deviceManager.js.map +0 -1
  94. package/dist/devices/airConditioner.d.ts +0 -98
  95. package/dist/devices/airConditioner.d.ts.map +0 -1
  96. package/dist/devices/airConditioner.js.map +0 -1
  97. package/dist/devices/batteryStorage.d.ts +0 -48
  98. package/dist/devices/batteryStorage.d.ts.map +0 -1
  99. package/dist/devices/batteryStorage.js.map +0 -1
  100. package/dist/devices/cooktop.d.ts +0 -60
  101. package/dist/devices/cooktop.d.ts.map +0 -1
  102. package/dist/devices/cooktop.js.map +0 -1
  103. package/dist/devices/dishwasher.d.ts +0 -71
  104. package/dist/devices/dishwasher.d.ts.map +0 -1
  105. package/dist/devices/dishwasher.js.map +0 -1
  106. package/dist/devices/evse.d.ts +0 -75
  107. package/dist/devices/evse.d.ts.map +0 -1
  108. package/dist/devices/evse.js.map +0 -1
  109. package/dist/devices/export.d.ts +0 -17
  110. package/dist/devices/export.d.ts.map +0 -1
  111. package/dist/devices/export.js.map +0 -1
  112. package/dist/devices/extractorHood.d.ts +0 -46
  113. package/dist/devices/extractorHood.d.ts.map +0 -1
  114. package/dist/devices/extractorHood.js.map +0 -1
  115. package/dist/devices/heatPump.d.ts +0 -47
  116. package/dist/devices/heatPump.d.ts.map +0 -1
  117. package/dist/devices/heatPump.js.map +0 -1
  118. package/dist/devices/laundryDryer.d.ts +0 -67
  119. package/dist/devices/laundryDryer.d.ts.map +0 -1
  120. package/dist/devices/laundryDryer.js.map +0 -1
  121. package/dist/devices/laundryWasher.d.ts +0 -81
  122. package/dist/devices/laundryWasher.d.ts.map +0 -1
  123. package/dist/devices/laundryWasher.js.map +0 -1
  124. package/dist/devices/microwaveOven.d.ts +0 -168
  125. package/dist/devices/microwaveOven.d.ts.map +0 -1
  126. package/dist/devices/microwaveOven.js.map +0 -1
  127. package/dist/devices/oven.d.ts +0 -105
  128. package/dist/devices/oven.d.ts.map +0 -1
  129. package/dist/devices/oven.js.map +0 -1
  130. package/dist/devices/refrigerator.d.ts +0 -118
  131. package/dist/devices/refrigerator.d.ts.map +0 -1
  132. package/dist/devices/refrigerator.js.map +0 -1
  133. package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
  134. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  135. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  136. package/dist/devices/solarPower.d.ts +0 -40
  137. package/dist/devices/solarPower.d.ts.map +0 -1
  138. package/dist/devices/solarPower.js.map +0 -1
  139. package/dist/devices/speaker.d.ts +0 -87
  140. package/dist/devices/speaker.d.ts.map +0 -1
  141. package/dist/devices/speaker.js.map +0 -1
  142. package/dist/devices/temperatureControl.d.ts +0 -166
  143. package/dist/devices/temperatureControl.d.ts.map +0 -1
  144. package/dist/devices/temperatureControl.js.map +0 -1
  145. package/dist/devices/waterHeater.d.ts +0 -111
  146. package/dist/devices/waterHeater.d.ts.map +0 -1
  147. package/dist/devices/waterHeater.js.map +0 -1
  148. package/dist/dgram/coap.d.ts +0 -205
  149. package/dist/dgram/coap.d.ts.map +0 -1
  150. package/dist/dgram/coap.js.map +0 -1
  151. package/dist/dgram/dgram.d.ts +0 -141
  152. package/dist/dgram/dgram.d.ts.map +0 -1
  153. package/dist/dgram/dgram.js.map +0 -1
  154. package/dist/dgram/mb_coap.d.ts +0 -24
  155. package/dist/dgram/mb_coap.d.ts.map +0 -1
  156. package/dist/dgram/mb_coap.js.map +0 -1
  157. package/dist/dgram/mb_mdns.d.ts +0 -24
  158. package/dist/dgram/mb_mdns.d.ts.map +0 -1
  159. package/dist/dgram/mb_mdns.js.map +0 -1
  160. package/dist/dgram/mdns.d.ts +0 -290
  161. package/dist/dgram/mdns.d.ts.map +0 -1
  162. package/dist/dgram/mdns.js.map +0 -1
  163. package/dist/dgram/multicast.d.ts +0 -67
  164. package/dist/dgram/multicast.d.ts.map +0 -1
  165. package/dist/dgram/multicast.js.map +0 -1
  166. package/dist/dgram/unicast.d.ts +0 -56
  167. package/dist/dgram/unicast.d.ts.map +0 -1
  168. package/dist/dgram/unicast.js.map +0 -1
  169. package/dist/frontend.d.ts +0 -232
  170. package/dist/frontend.d.ts.map +0 -1
  171. package/dist/frontend.js.map +0 -1
  172. package/dist/frontendTypes.d.ts +0 -514
  173. package/dist/frontendTypes.d.ts.map +0 -1
  174. package/dist/frontendTypes.js.map +0 -1
  175. package/dist/globalMatterbridge.d.ts +0 -59
  176. package/dist/globalMatterbridge.d.ts.map +0 -1
  177. package/dist/globalMatterbridge.js +0 -70
  178. package/dist/globalMatterbridge.js.map +0 -1
  179. package/dist/helpers.d.ts +0 -48
  180. package/dist/helpers.d.ts.map +0 -1
  181. package/dist/helpers.js.map +0 -1
  182. package/dist/index.d.ts +0 -33
  183. package/dist/index.d.ts.map +0 -1
  184. package/dist/index.js.map +0 -1
  185. package/dist/logger/export.d.ts +0 -2
  186. package/dist/logger/export.d.ts.map +0 -1
  187. package/dist/logger/export.js.map +0 -1
  188. package/dist/matter/behaviors.d.ts +0 -2
  189. package/dist/matter/behaviors.d.ts.map +0 -1
  190. package/dist/matter/behaviors.js.map +0 -1
  191. package/dist/matter/clusters.d.ts +0 -2
  192. package/dist/matter/clusters.d.ts.map +0 -1
  193. package/dist/matter/clusters.js.map +0 -1
  194. package/dist/matter/devices.d.ts +0 -2
  195. package/dist/matter/devices.d.ts.map +0 -1
  196. package/dist/matter/devices.js.map +0 -1
  197. package/dist/matter/endpoints.d.ts +0 -2
  198. package/dist/matter/endpoints.d.ts.map +0 -1
  199. package/dist/matter/endpoints.js.map +0 -1
  200. package/dist/matter/export.d.ts +0 -5
  201. package/dist/matter/export.d.ts.map +0 -1
  202. package/dist/matter/export.js.map +0 -1
  203. package/dist/matter/types.d.ts +0 -3
  204. package/dist/matter/types.d.ts.map +0 -1
  205. package/dist/matter/types.js.map +0 -1
  206. package/dist/matterbridge.d.ts +0 -430
  207. package/dist/matterbridge.d.ts.map +0 -1
  208. package/dist/matterbridge.js.map +0 -1
  209. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  210. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  211. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  212. package/dist/matterbridgeBehaviors.d.ts +0 -1747
  213. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  214. package/dist/matterbridgeBehaviors.js.map +0 -1
  215. package/dist/matterbridgeDeviceTypes.d.ts +0 -761
  216. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  217. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  218. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  219. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  220. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  221. package/dist/matterbridgeEndpoint.d.ts +0 -1534
  222. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  223. package/dist/matterbridgeEndpoint.js.map +0 -1
  224. package/dist/matterbridgeEndpointHelpers.d.ts +0 -407
  225. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  226. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  227. package/dist/matterbridgePlatform.d.ts +0 -402
  228. package/dist/matterbridgePlatform.d.ts.map +0 -1
  229. package/dist/matterbridgePlatform.js.map +0 -1
  230. package/dist/matterbridgeTypes.d.ts +0 -201
  231. package/dist/matterbridgeTypes.d.ts.map +0 -1
  232. package/dist/matterbridgeTypes.js.map +0 -1
  233. package/dist/pluginManager.d.ts +0 -270
  234. package/dist/pluginManager.d.ts.map +0 -1
  235. package/dist/pluginManager.js.map +0 -1
  236. package/dist/shelly.d.ts +0 -174
  237. package/dist/shelly.d.ts.map +0 -1
  238. package/dist/shelly.js.map +0 -1
  239. package/dist/storage/export.d.ts +0 -2
  240. package/dist/storage/export.d.ts.map +0 -1
  241. package/dist/storage/export.js.map +0 -1
  242. package/dist/update.d.ts +0 -75
  243. package/dist/update.d.ts.map +0 -1
  244. package/dist/update.js.map +0 -1
  245. package/dist/utils/colorUtils.d.ts +0 -99
  246. package/dist/utils/colorUtils.d.ts.map +0 -1
  247. package/dist/utils/colorUtils.js.map +0 -1
  248. package/dist/utils/commandLine.d.ts +0 -59
  249. package/dist/utils/commandLine.d.ts.map +0 -1
  250. package/dist/utils/commandLine.js.map +0 -1
  251. package/dist/utils/copyDirectory.d.ts +0 -33
  252. package/dist/utils/copyDirectory.d.ts.map +0 -1
  253. package/dist/utils/copyDirectory.js.map +0 -1
  254. package/dist/utils/createDirectory.d.ts +0 -34
  255. package/dist/utils/createDirectory.d.ts.map +0 -1
  256. package/dist/utils/createDirectory.js.map +0 -1
  257. package/dist/utils/createZip.d.ts +0 -39
  258. package/dist/utils/createZip.d.ts.map +0 -1
  259. package/dist/utils/createZip.js.map +0 -1
  260. package/dist/utils/deepCopy.d.ts +0 -32
  261. package/dist/utils/deepCopy.d.ts.map +0 -1
  262. package/dist/utils/deepCopy.js.map +0 -1
  263. package/dist/utils/deepEqual.d.ts +0 -54
  264. package/dist/utils/deepEqual.d.ts.map +0 -1
  265. package/dist/utils/deepEqual.js.map +0 -1
  266. package/dist/utils/error.d.ts +0 -44
  267. package/dist/utils/error.d.ts.map +0 -1
  268. package/dist/utils/error.js.map +0 -1
  269. package/dist/utils/export.d.ts +0 -13
  270. package/dist/utils/export.d.ts.map +0 -1
  271. package/dist/utils/export.js.map +0 -1
  272. package/dist/utils/hex.d.ts +0 -89
  273. package/dist/utils/hex.d.ts.map +0 -1
  274. package/dist/utils/hex.js.map +0 -1
  275. package/dist/utils/isvalid.d.ts +0 -103
  276. package/dist/utils/isvalid.d.ts.map +0 -1
  277. package/dist/utils/isvalid.js.map +0 -1
  278. package/dist/utils/jestHelpers.d.ts +0 -137
  279. package/dist/utils/jestHelpers.d.ts.map +0 -1
  280. package/dist/utils/jestHelpers.js.map +0 -1
  281. package/dist/utils/network.d.ts +0 -84
  282. package/dist/utils/network.d.ts.map +0 -1
  283. package/dist/utils/network.js.map +0 -1
  284. package/dist/utils/spawn.d.ts +0 -34
  285. package/dist/utils/spawn.d.ts.map +0 -1
  286. package/dist/utils/spawn.js.map +0 -1
  287. package/dist/utils/wait.d.ts +0 -54
  288. package/dist/utils/wait.d.ts.map +0 -1
  289. package/dist/utils/wait.js.map +0 -1
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.3.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,22 +5,20 @@ 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
- import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
38
- // @matter
11
+ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
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
15
  import { CommissioningOptions } from '@matter/main/types';
43
- // Matterbridge
16
+ import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
44
17
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
45
- import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from './matterbridgeTypes.js';
18
+ import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
46
19
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
47
20
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
21
+ import { BroadcastServer } from './broadcastServer.js';
48
22
  export class Frontend extends EventEmitter {
49
23
  matterbridge;
50
24
  log;
@@ -54,11 +28,55 @@ export class Frontend extends EventEmitter {
54
28
  httpServer;
55
29
  httpsServer;
56
30
  webSocketServer;
31
+ server;
57
32
  constructor(matterbridge) {
58
33
  super();
59
34
  this.matterbridge = matterbridge;
60
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
35
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
61
36
  this.log.logNameColor = '\x1b[38;5;97m';
37
+ this.server = new BroadcastServer('plugins', this.log);
38
+ this.server.on('broadcast_message', this.msgHandler.bind(this));
39
+ }
40
+ destroy() {
41
+ this.server.close();
42
+ }
43
+ async msgHandler(msg) {
44
+ if (this.server.isWorkerRequest(msg, msg.type)) {
45
+ this.log.debug(`**Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
46
+ switch (msg.type) {
47
+ default:
48
+ this.log.warn(`Unknown broadcast request ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
49
+ }
50
+ }
51
+ if (this.server.isWorkerResponse(msg, msg.type)) {
52
+ this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
53
+ switch (msg.type) {
54
+ case 'plugins_install':
55
+ this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
56
+ if (msg.response.success) {
57
+ this.wssSendRestartRequired(true, true);
58
+ this.wssSendRefreshRequired('plugins');
59
+ this.wssSendSnackbarMessage(`Installed package ${msg.response.packageName}`, 5, 'success');
60
+ }
61
+ else {
62
+ this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not installed`, 10, 'error');
63
+ }
64
+ break;
65
+ case 'plugins_uninstall':
66
+ this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.response.packageName}...`);
67
+ if (msg.response.success) {
68
+ this.wssSendRestartRequired(true, true);
69
+ this.wssSendRefreshRequired('plugins');
70
+ this.wssSendSnackbarMessage(`Uninstalled package ${msg.response.packageName}`, 5, 'success');
71
+ }
72
+ else {
73
+ this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not uninstalled`, 10, 'error');
74
+ }
75
+ break;
76
+ default:
77
+ this.log.warn(`Unknown broadcast response ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
78
+ }
79
+ }
62
80
  }
63
81
  set logLevel(logLevel) {
64
82
  this.log.logLevel = logLevel;
@@ -66,51 +84,11 @@ export class Frontend extends EventEmitter {
66
84
  async start(port = 8283) {
67
85
  this.port = port;
68
86
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
69
- // Initialize multer with the upload directory
70
- const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
87
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
71
88
  const upload = multer({ dest: uploadDir });
72
- // Create the express app that serves the frontend
73
89
  this.expressApp = express();
74
- // Inject logging/debug wrapper for route/middleware registration
75
- /*
76
- const methods = ['get', 'post', 'put', 'delete', 'use'];
77
- for (const method of methods) {
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- const original = (this.expressApp as any)[method].bind(this.expressApp);
80
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
82
- try {
83
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
84
- return original(path, ...rest);
85
- } catch (err) {
86
- console.error(`[ERROR] Failed to register route: ${path}`);
87
- throw err;
88
- }
89
- };
90
- }
91
- */
92
- // Log all requests to the server for debugging
93
- /*
94
- this.expressApp.use((req, res, next) => {
95
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
96
- next();
97
- });
98
- */
99
- // Serve static files from '/static' endpoint
100
90
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
101
- // Read the package.json file to get the frontend version
102
- try {
103
- this.log.debug(`Reading frontend package.json...`);
104
- const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
105
- this.matterbridge.matterbridgeInformation.frontendVersion = JSON.parse(frontendJson)?.version;
106
- this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
107
- }
108
- catch (error) {
109
- // istanbul ignore next
110
- this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
111
- }
112
91
  if (!hasParameter('ssl')) {
113
- // Create an HTTP server and attach the express app
114
92
  try {
115
93
  this.log.debug(`Creating HTTP server...`);
116
94
  this.httpServer = createServer(this.expressApp);
@@ -120,7 +98,6 @@ export class Frontend extends EventEmitter {
120
98
  this.emit('server_error', error);
121
99
  return;
122
100
  }
123
- // Listen on the specified port
124
101
  if (hasParameter('ingress')) {
125
102
  this.httpServer.listen(this.port, '0.0.0.0', () => {
126
103
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -161,7 +138,6 @@ export class Frontend extends EventEmitter {
161
138
  let passphrase;
162
139
  let httpsServerOptions = {};
163
140
  if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
164
- // Load the p12 certificate and the passphrase
165
141
  try {
166
142
  pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
167
143
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
@@ -173,7 +149,7 @@ export class Frontend extends EventEmitter {
173
149
  }
174
150
  try {
175
151
  passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
176
- passphrase = passphrase.trim(); // Ensure no extra characters
152
+ passphrase = passphrase.trim();
177
153
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
178
154
  }
179
155
  catch (error) {
@@ -184,7 +160,6 @@ export class Frontend extends EventEmitter {
184
160
  httpsServerOptions = { pfx, passphrase };
185
161
  }
186
162
  else {
187
- // 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.
188
163
  try {
189
164
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
190
165
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
@@ -214,10 +189,9 @@ export class Frontend extends EventEmitter {
214
189
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
215
190
  }
216
191
  if (hasParameter('mtls')) {
217
- httpsServerOptions.requestCert = true; // Request client certificate
218
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
192
+ httpsServerOptions.requestCert = true;
193
+ httpsServerOptions.rejectUnauthorized = true;
219
194
  }
220
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
221
195
  try {
222
196
  this.log.debug(`Creating HTTPS server...`);
223
197
  this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
@@ -227,7 +201,6 @@ export class Frontend extends EventEmitter {
227
201
  this.emit('server_error', error);
228
202
  return;
229
203
  }
230
- // Listen on the specified port
231
204
  if (hasParameter('ingress')) {
232
205
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
233
206
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -259,19 +232,15 @@ export class Frontend extends EventEmitter {
259
232
  return;
260
233
  });
261
234
  }
262
- // Create a WebSocket server and attach it to the http or https server
263
- const wssPort = this.port;
264
- const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
265
- this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
235
+ this.log.debug(`Creating WebSocketServer...`);
266
236
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
267
237
  this.webSocketServer.on('connection', (ws, request) => {
268
238
  const clientIp = request.socket.remoteAddress;
269
- // Set the global logger callback for the WebSocketServer
270
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
271
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
272
- callbackLogLevel = "info" /* LogLevel.INFO */;
273
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
274
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
239
+ let callbackLogLevel = "notice";
240
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
241
+ callbackLogLevel = "info";
242
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
243
+ callbackLogLevel = "debug";
275
244
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
276
245
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
277
246
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -293,7 +262,6 @@ export class Frontend extends EventEmitter {
293
262
  }
294
263
  });
295
264
  ws.on('error', (error) => {
296
- // istanbul ignore next
297
265
  this.log.error(`WebSocket client error: ${error}`);
298
266
  });
299
267
  });
@@ -301,13 +269,12 @@ export class Frontend extends EventEmitter {
301
269
  this.log.debug(`WebSocketServer closed`);
302
270
  });
303
271
  this.webSocketServer.on('listening', () => {
304
- this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
305
- this.emit('websocket_server_listening', wssHost);
272
+ this.log.info(`The WebSocketServer is listening`);
273
+ this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
306
274
  });
307
275
  this.webSocketServer.on('error', (ws, error) => {
308
276
  this.log.error(`WebSocketServer error: ${error}`);
309
277
  });
310
- // Subscribe to cli events
311
278
  cliEmitter.removeAllListeners();
312
279
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
313
280
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -318,8 +285,6 @@ export class Frontend extends EventEmitter {
318
285
  cliEmitter.on('cpu', (cpuUsage) => {
319
286
  this.wssSendCpuUpdate(cpuUsage);
320
287
  });
321
- // Endpoint to validate login code
322
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
323
288
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
324
289
  const { password } = req.body;
325
290
  this.log.debug('The frontend sent /api/login', password);
@@ -338,48 +303,41 @@ export class Frontend extends EventEmitter {
338
303
  this.log.warn('/api/login error wrong password');
339
304
  res.json({ valid: false });
340
305
  }
341
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
342
306
  }
343
307
  catch (error) {
344
308
  this.log.error('/api/login error getting password');
345
309
  res.json({ valid: false });
346
310
  }
347
311
  });
348
- // Endpoint to provide health check for docker
349
312
  this.expressApp.get('/health', (req, res) => {
350
313
  this.log.debug('Express received /health');
351
314
  const healthStatus = {
352
- status: 'ok', // Indicate service is healthy
353
- uptime: process.uptime(), // Server uptime in seconds
354
- timestamp: new Date().toISOString(), // Current timestamp
315
+ status: 'ok',
316
+ uptime: process.uptime(),
317
+ timestamp: new Date().toISOString(),
355
318
  };
356
319
  res.status(200).json(healthStatus);
357
320
  });
358
- // Endpoint to provide memory usage details
359
321
  this.expressApp.get('/memory', async (req, res) => {
360
322
  this.log.debug('Express received /memory');
361
- // Memory usage from process
362
323
  const memoryUsageRaw = process.memoryUsage();
363
324
  const memoryUsage = {
364
- rss: this.formatMemoryUsage(memoryUsageRaw.rss),
365
- heapTotal: this.formatMemoryUsage(memoryUsageRaw.heapTotal),
366
- heapUsed: this.formatMemoryUsage(memoryUsageRaw.heapUsed),
367
- external: this.formatMemoryUsage(memoryUsageRaw.external),
368
- arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
325
+ rss: formatMemoryUsage(memoryUsageRaw.rss),
326
+ heapTotal: formatMemoryUsage(memoryUsageRaw.heapTotal),
327
+ heapUsed: formatMemoryUsage(memoryUsageRaw.heapUsed),
328
+ external: formatMemoryUsage(memoryUsageRaw.external),
329
+ arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
369
330
  };
370
- // V8 heap statistics
371
331
  const { default: v8 } = await import('node:v8');
372
332
  const heapStatsRaw = v8.getHeapStatistics();
373
333
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
374
- // Format heapStats
375
- const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
376
- // Format heapSpaces
334
+ const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
377
335
  const heapSpaces = heapSpacesRaw.map((space) => ({
378
336
  ...space,
379
- space_size: this.formatMemoryUsage(space.space_size),
380
- space_used_size: this.formatMemoryUsage(space.space_used_size),
381
- space_available_size: this.formatMemoryUsage(space.space_available_size),
382
- physical_space_size: this.formatMemoryUsage(space.physical_space_size),
337
+ space_size: formatMemoryUsage(space.space_size),
338
+ space_used_size: formatMemoryUsage(space.space_used_size),
339
+ space_available_size: formatMemoryUsage(space.space_available_size),
340
+ physical_space_size: formatMemoryUsage(space.physical_space_size),
383
341
  }));
384
342
  const { createRequire } = await import('node:module');
385
343
  const require = createRequire(import.meta.url);
@@ -392,23 +350,18 @@ export class Frontend extends EventEmitter {
392
350
  };
393
351
  res.status(200).json(memoryReport);
394
352
  });
395
- // Endpoint to provide settings
396
353
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
397
354
  this.log.debug('The frontend sent /api/settings');
398
355
  res.json(await this.getApiSettings());
399
356
  });
400
- // Endpoint to provide plugins
401
357
  this.expressApp.get('/api/plugins', async (req, res) => {
402
358
  this.log.debug('The frontend sent /api/plugins');
403
- res.json(this.getPlugins());
359
+ res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
404
360
  });
405
- // Endpoint to provide devices
406
361
  this.expressApp.get('/api/devices', async (req, res) => {
407
362
  this.log.debug('The frontend sent /api/devices');
408
- const devices = this.getDevices();
409
- res.json(devices);
363
+ res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
410
364
  });
411
- // Endpoint to view the matterbridge log
412
365
  this.expressApp.get('/api/view-mblog', async (req, res) => {
413
366
  this.log.debug('The frontend sent /api/view-mblog');
414
367
  try {
@@ -421,7 +374,6 @@ export class Frontend extends EventEmitter {
421
374
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
422
375
  }
423
376
  });
424
- // Endpoint to view the matter.js log
425
377
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
426
378
  this.log.debug('The frontend sent /api/view-mjlog');
427
379
  try {
@@ -434,11 +386,9 @@ export class Frontend extends EventEmitter {
434
386
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
435
387
  }
436
388
  });
437
- // Endpoint to view the diagnostic.log
438
389
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
439
390
  this.log.debug('The frontend sent /api/view-diagnostic');
440
391
  const serverNodes = [];
441
- // istanbul ignore else
442
392
  if (this.matterbridge.bridgeMode === 'bridge') {
443
393
  if (this.matterbridge.serverNode)
444
394
  serverNodes.push(this.matterbridge.serverNode);
@@ -449,7 +399,6 @@ export class Frontend extends EventEmitter {
449
399
  serverNodes.push(plugin.serverNode);
450
400
  }
451
401
  }
452
- // istanbul ignore next
453
402
  for (const device of this.matterbridge.devices.array()) {
454
403
  if (device.serverNode)
455
404
  serverNodes.push(device.serverNode);
@@ -472,20 +421,17 @@ export class Frontend extends EventEmitter {
472
421
  values: [...serverNodes],
473
422
  })));
474
423
  delete Logger.destinations.diagnostic;
475
- await wait(500); // Wait for the log to be written
424
+ await wait(500);
476
425
  try {
477
426
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
478
427
  res.type('text/plain');
479
428
  res.send(data.slice(29));
480
429
  }
481
430
  catch (error) {
482
- // istanbul ignore next
483
431
  this.log.error(`Error reading diagnostic log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
484
- // istanbul ignore next
485
432
  res.status(500).send('Error reading diagnostic log file.');
486
433
  }
487
434
  });
488
- // Endpoint to view the shelly log
489
435
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
490
436
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
491
437
  try {
@@ -498,7 +444,6 @@ export class Frontend extends EventEmitter {
498
444
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
499
445
  }
500
446
  });
501
- // Endpoint to download the matterbridge log
502
447
  this.expressApp.get('/api/download-mblog', async (req, res) => {
503
448
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
504
449
  try {
@@ -512,14 +457,12 @@ export class Frontend extends EventEmitter {
512
457
  }
513
458
  res.type('text/plain');
514
459
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
515
- /* istanbul ignore if */
516
460
  if (error) {
517
461
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
518
462
  res.status(500).send('Error downloading the matterbridge log file');
519
463
  }
520
464
  });
521
465
  });
522
- // Endpoint to download the matter log
523
466
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
524
467
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
525
468
  try {
@@ -533,14 +476,12 @@ export class Frontend extends EventEmitter {
533
476
  }
534
477
  res.type('text/plain');
535
478
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
536
- /* istanbul ignore if */
537
479
  if (error) {
538
480
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
539
481
  res.status(500).send('Error downloading the matter log file');
540
482
  }
541
483
  });
542
484
  });
543
- // Endpoint to download the shelly log
544
485
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
545
486
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
546
487
  try {
@@ -554,93 +495,77 @@ export class Frontend extends EventEmitter {
554
495
  }
555
496
  res.type('text/plain');
556
497
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
557
- /* istanbul ignore if */
558
498
  if (error) {
559
499
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
560
500
  res.status(500).send('Error downloading Shelly system log file');
561
501
  }
562
502
  });
563
503
  });
564
- // Endpoint to download the matterbridge storage directory
565
504
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
566
505
  this.log.debug('The frontend sent /api/download-mbstorage');
567
506
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
568
507
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
569
- /* istanbul ignore if */
570
508
  if (error) {
571
509
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
572
510
  res.status(500).send('Error downloading the matterbridge storage file');
573
511
  }
574
512
  });
575
513
  });
576
- // Endpoint to download the matter storage file
577
514
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
578
515
  this.log.debug('The frontend sent /api/download-mjstorage');
579
516
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
580
517
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
581
- /* istanbul ignore if */
582
518
  if (error) {
583
519
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
584
520
  res.status(500).send('Error downloading the matter storage zip file');
585
521
  }
586
522
  });
587
523
  });
588
- // Endpoint to download the matterbridge plugin directory
589
524
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
590
525
  this.log.debug('The frontend sent /api/download-pluginstorage');
591
526
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
592
527
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
593
- /* istanbul ignore if */
594
528
  if (error) {
595
529
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
596
530
  res.status(500).send('Error downloading the matterbridge plugin storage file');
597
531
  }
598
532
  });
599
533
  });
600
- // Endpoint to download the matterbridge plugin config files
601
534
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
602
535
  this.log.debug('The frontend sent /api/download-pluginconfig');
603
536
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
604
537
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
605
- /* istanbul ignore if */
606
538
  if (error) {
607
539
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
608
540
  res.status(500).send('Error downloading the matterbridge plugin config file');
609
541
  }
610
542
  });
611
543
  });
612
- // Endpoint to download the matterbridge backup (created with the backup command)
613
544
  this.expressApp.get('/api/download-backup', async (req, res) => {
614
545
  this.log.debug('The frontend sent /api/download-backup');
615
546
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
616
- /* istanbul ignore if */
617
547
  if (error) {
618
548
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
619
549
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
620
550
  }
621
551
  });
622
552
  });
623
- // Endpoint to upload a package
624
553
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
625
554
  const { filename } = req.body;
626
555
  const file = req.file;
627
- /* istanbul ignore if */
628
556
  if (!file || !filename) {
629
557
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
630
558
  res.status(400).send('Invalid request: file and filename are required');
631
559
  return;
632
560
  }
633
561
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
634
- // Define the path where the plugin file will be saved
635
562
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
636
563
  try {
637
- // Move the uploaded file to the specified path
638
564
  await fs.rename(file.path, filePath);
639
565
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
640
- // Install the plugin package
641
566
  if (filename.endsWith('.tgz')) {
642
567
  const { spawnCommand } = await import('./utils/spawn.js');
643
- await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
568
+ await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
644
569
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
645
570
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
646
571
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
@@ -657,7 +582,6 @@ export class Frontend extends EventEmitter {
657
582
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
658
583
  }
659
584
  });
660
- // Fallback for routing (must be the last route)
661
585
  this.expressApp.use((req, res) => {
662
586
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
663
587
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -666,16 +590,13 @@ export class Frontend extends EventEmitter {
666
590
  }
667
591
  async stop() {
668
592
  this.log.debug('Stopping the frontend...');
669
- // Remove listeners from the express app
670
593
  if (this.expressApp) {
671
594
  this.expressApp.removeAllListeners();
672
595
  this.expressApp = undefined;
673
596
  this.log.debug('Frontend app closed successfully');
674
597
  }
675
- // Close the WebSocket server
676
598
  if (this.webSocketServer) {
677
599
  this.log.debug('Closing WebSocket server...');
678
- // Close all active connections
679
600
  this.webSocketServer.clients.forEach((client) => {
680
601
  if (client.readyState === WebSocket.OPEN) {
681
602
  client.close();
@@ -684,7 +605,6 @@ export class Frontend extends EventEmitter {
684
605
  await withTimeout(new Promise((resolve) => {
685
606
  this.webSocketServer?.close((error) => {
686
607
  if (error) {
687
- // istanbul ignore next
688
608
  this.log.error(`Error closing WebSocket server: ${error}`);
689
609
  }
690
610
  else {
@@ -697,27 +617,8 @@ export class Frontend extends EventEmitter {
697
617
  this.webSocketServer.removeAllListeners();
698
618
  this.webSocketServer = undefined;
699
619
  }
700
- // Close the http server
701
620
  if (this.httpServer) {
702
621
  this.log.debug('Closing http server...');
703
- /*
704
- await withTimeout(
705
- new Promise<void>((resolve) => {
706
- this.httpServer?.close((error) => {
707
- if (error) {
708
- // istanbul ignore next
709
- this.log.error(`Error closing http server: ${error}`);
710
- } else {
711
- this.log.debug('Http server closed successfully');
712
- this.emit('server_stopped');
713
- }
714
- resolve();
715
- });
716
- }),
717
- 5000,
718
- false,
719
- );
720
- */
721
622
  this.httpServer.close();
722
623
  this.log.debug('Http server closed successfully');
723
624
  this.listening = false;
@@ -726,27 +627,8 @@ export class Frontend extends EventEmitter {
726
627
  this.httpServer = undefined;
727
628
  this.log.debug('Frontend http server closed successfully');
728
629
  }
729
- // Close the https server
730
630
  if (this.httpsServer) {
731
631
  this.log.debug('Closing https server...');
732
- /*
733
- await withTimeout(
734
- new Promise<void>((resolve) => {
735
- this.httpsServer?.close((error) => {
736
- if (error) {
737
- // istanbul ignore next
738
- this.log.error(`Error closing https server: ${error}`);
739
- } else {
740
- this.log.debug('Https server closed successfully');
741
- this.emit('server_stopped');
742
- }
743
- resolve();
744
- });
745
- }),
746
- 5000,
747
- false,
748
- );
749
- */
750
632
  this.httpsServer.close();
751
633
  this.log.debug('Https server closed successfully');
752
634
  this.listening = false;
@@ -757,69 +639,50 @@ export class Frontend extends EventEmitter {
757
639
  }
758
640
  this.log.debug('Frontend stopped successfully');
759
641
  }
760
- // Function to format bytes to KB, MB, or GB
761
- formatMemoryUsage = (bytes) => {
762
- if (bytes >= 1024 ** 3) {
763
- return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
764
- }
765
- else if (bytes >= 1024 ** 2) {
766
- return `${(bytes / 1024 ** 2).toFixed(2)} MB`;
767
- }
768
- else {
769
- return `${(bytes / 1024).toFixed(2)} KB`;
770
- }
771
- };
772
- // Function to format system uptime with only the most significant unit
773
- formatOsUpTime = (seconds) => {
774
- if (seconds >= 86400) {
775
- const days = Math.floor(seconds / 86400);
776
- return `${days} day${days !== 1 ? 's' : ''}`;
777
- }
778
- if (seconds >= 3600) {
779
- const hours = Math.floor(seconds / 3600);
780
- return `${hours} hour${hours !== 1 ? 's' : ''}`;
781
- }
782
- if (seconds >= 60) {
783
- const minutes = Math.floor(seconds / 60);
784
- return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
785
- }
786
- return `${seconds} second${seconds !== 1 ? 's' : ''}`;
787
- };
788
- /**
789
- * Retrieves the api settings data.
790
- *
791
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
792
- */
793
642
  async getApiSettings() {
794
- // Update the system information
795
- this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
796
- this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
797
- this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
798
- this.matterbridge.systemInformation.processUptime = this.formatOsUpTime(Math.floor(process.uptime()));
643
+ this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
644
+ this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
645
+ this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
646
+ this.matterbridge.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
799
647
  this.matterbridge.systemInformation.cpuUsage = lastCpuUsage.toFixed(2) + ' %';
800
- this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
801
- this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
802
- this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
803
- // Update the matterbridge information
804
- this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
805
- this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
806
- this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
807
- this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
808
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
809
- this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
810
- this.matterbridge.matterbridgeInformation.matterIpv4Address = this.matterbridge.ipv4address;
811
- this.matterbridge.matterbridgeInformation.matterIpv6Address = this.matterbridge.ipv6address;
812
- this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
813
- this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
814
- this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
815
- return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
648
+ this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
649
+ this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
650
+ this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
651
+ const info = {
652
+ homeDirectory: this.matterbridge.homeDirectory,
653
+ rootDirectory: this.matterbridge.rootDirectory,
654
+ matterbridgeDirectory: this.matterbridge.matterbridgeDirectory,
655
+ matterbridgePluginDirectory: this.matterbridge.matterbridgePluginDirectory,
656
+ matterbridgeCertDirectory: this.matterbridge.matterbridgeCertDirectory,
657
+ globalModulesDirectory: this.matterbridge.globalModulesDirectory,
658
+ matterbridgeVersion: this.matterbridge.matterbridgeVersion,
659
+ matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
660
+ matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
661
+ frontendVersion: this.matterbridge.frontendVersion,
662
+ bridgeMode: this.matterbridge.bridgeMode,
663
+ restartMode: this.matterbridge.restartMode,
664
+ virtualMode: this.matterbridge.virtualMode,
665
+ profile: this.matterbridge.profile,
666
+ readOnly: this.matterbridge.readOnly,
667
+ shellyBoard: this.matterbridge.shellyBoard,
668
+ shellySysUpdate: this.matterbridge.shellySysUpdate,
669
+ shellyMainUpdate: this.matterbridge.shellyMainUpdate,
670
+ loggerLevel: await this.matterbridge.getLogLevel(),
671
+ fileLogger: this.matterbridge.fileLogger,
672
+ matterLoggerLevel: Logger.level,
673
+ matterFileLogger: this.matterbridge.matterFileLogger,
674
+ matterMdnsInterface: this.matterbridge.mdnsInterface,
675
+ matterIpv4Address: this.matterbridge.ipv4Address,
676
+ matterIpv6Address: this.matterbridge.ipv6Address,
677
+ matterPort: (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540,
678
+ matterDiscriminator: await this.matterbridge.nodeContext?.get('matterdiscriminator'),
679
+ matterPasscode: await this.matterbridge.nodeContext?.get('matterpasscode'),
680
+ restartRequired: this.matterbridge.restartRequired,
681
+ fixedRestartRequired: this.matterbridge.fixedRestartRequired,
682
+ updateRequired: this.matterbridge.updateRequired,
683
+ };
684
+ return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
816
685
  }
817
- /**
818
- * Retrieves the reachable attribute.
819
- *
820
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
821
- * @returns {boolean} The reachable attribute.
822
- */
823
686
  getReachability(device) {
824
687
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
825
688
  return false;
@@ -831,12 +694,6 @@ export class Frontend extends EventEmitter {
831
694
  return true;
832
695
  return false;
833
696
  }
834
- /**
835
- * Retrieves the power source attribute.
836
- *
837
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
838
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
839
- */
840
697
  getPowerSource(endpoint) {
841
698
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
842
699
  return undefined;
@@ -852,22 +709,13 @@ export class Frontend extends EventEmitter {
852
709
  }
853
710
  return;
854
711
  };
855
- // Root endpoint
856
712
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
857
713
  return powerSource(endpoint);
858
- // Child endpoints
859
714
  for (const child of endpoint.getChildEndpoints()) {
860
715
  if (child.hasClusterServer(PowerSource.Cluster.id))
861
716
  return powerSource(child);
862
717
  }
863
718
  }
864
- /**
865
- * Retrieves the cluster text description from a given device.
866
- * The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
867
- *
868
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
869
- * @returns {string} The attributes description of the cluster servers in the device.
870
- */
871
719
  getClusterTextFromDevice(device) {
872
720
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
873
721
  return '';
@@ -878,7 +726,6 @@ export class Frontend extends EventEmitter {
878
726
  if (composed)
879
727
  return 'Composed: ' + composed.value;
880
728
  }
881
- // istanbul ignore next cause is not reachable
882
729
  return '';
883
730
  };
884
731
  const getFixedLabel = (device) => {
@@ -888,13 +735,11 @@ export class Frontend extends EventEmitter {
888
735
  if (composed)
889
736
  return 'Composed: ' + composed.value;
890
737
  }
891
- // istanbul ignore next cause is not reacheable
892
738
  return '';
893
739
  };
894
740
  let attributes = '';
895
741
  let supportedModes = [];
896
742
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
897
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
898
743
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
899
744
  return;
900
745
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -984,20 +829,12 @@ export class Frontend extends EventEmitter {
984
829
  if (clusterName === 'userLabel' && attributeName === 'labelList')
985
830
  attributes += `${getUserLabel(device)} `;
986
831
  });
987
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
988
832
  return attributes.trimStart().trimEnd();
989
833
  }
990
- /**
991
- * Retrieves the registered plugins sanitized for res.json().
992
- *
993
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
994
- */
995
834
  getPlugins() {
996
- if (this.matterbridge.hasCleanupStarted)
997
- return []; // Skip if cleanup has started
998
- const baseRegisteredPlugins = [];
999
- for (const plugin of this.matterbridge.plugins) {
1000
- baseRegisteredPlugins.push({
835
+ const plugins = [];
836
+ for (const plugin of this.matterbridge.plugins.array()) {
837
+ plugins.push({
1001
838
  path: plugin.path,
1002
839
  type: plugin.type,
1003
840
  name: plugin.name,
@@ -1022,27 +859,16 @@ export class Frontend extends EventEmitter {
1022
859
  schemaJson: plugin.schemaJson,
1023
860
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1024
861
  hasBlackList: plugin.configJson?.blackList !== undefined,
1025
- // Childbridge mode specific data
1026
862
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1027
863
  });
1028
864
  }
1029
- return baseRegisteredPlugins;
865
+ return plugins;
1030
866
  }
1031
- /**
1032
- * Retrieves the devices from Matterbridge.
1033
- *
1034
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1035
- * @returns {ApiDevices[]} An array of ApiDevices for the frontend.
1036
- */
1037
867
  getDevices(pluginName) {
1038
- if (this.matterbridge.hasCleanupStarted)
1039
- return []; // Skip if cleanup has started
1040
868
  const devices = [];
1041
869
  for (const device of this.matterbridge.devices.array()) {
1042
- // Filter by pluginName if provided
1043
870
  if (pluginName && pluginName !== device.plugin)
1044
871
  continue;
1045
- // Check if the device has the required properties
1046
872
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1047
873
  continue;
1048
874
  devices.push({
@@ -1062,39 +888,24 @@ export class Frontend extends EventEmitter {
1062
888
  }
1063
889
  return devices;
1064
890
  }
1065
- /**
1066
- * Retrieves the clusters from a given plugin and endpoint number.
1067
- *
1068
- * Response for /api/clusters
1069
- *
1070
- * @param {string} pluginName - The name of the plugin.
1071
- * @param {number} endpointNumber - The endpoint number.
1072
- * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1073
- */
1074
891
  getClusters(pluginName, endpointNumber) {
1075
892
  if (this.matterbridge.hasCleanupStarted)
1076
- return; // Skip if cleanup has started
893
+ return;
1077
894
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1078
895
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1079
896
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1080
897
  return;
1081
898
  }
1082
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1083
- // Get the device types from the main endpoint
1084
899
  const deviceTypes = [];
1085
900
  const clusters = [];
1086
901
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1087
902
  deviceTypes.push(d.deviceType);
1088
903
  });
1089
- // Get the clusters from the main endpoint
1090
904
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1091
905
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1092
906
  return;
1093
907
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1094
908
  return;
1095
- // console.log(
1096
- // `${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}`,
1097
- // );
1098
909
  clusters.push({
1099
910
  endpoint: endpoint.number.toString(),
1100
911
  number: endpoint.number,
@@ -1108,19 +919,12 @@ export class Frontend extends EventEmitter {
1108
919
  attributeLocalValue: attributeValue,
1109
920
  });
1110
921
  });
1111
- // Get the child endpoints
1112
922
  const childEndpoints = endpoint.getChildEndpoints();
1113
- // if (childEndpoints.length === 0) {
1114
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1115
- // }
1116
923
  childEndpoints.forEach((childEndpoint) => {
1117
- // istanbul ignore if cause is not reachable: should never happen but ...
1118
924
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1119
925
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1120
926
  return;
1121
927
  }
1122
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1123
- // Get the device types of the child endpoint
1124
928
  const deviceTypes = [];
1125
929
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1126
930
  deviceTypes.push(d.deviceType);
@@ -1130,9 +934,6 @@ export class Frontend extends EventEmitter {
1130
934
  return;
1131
935
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1132
936
  return;
1133
- // console.log(
1134
- // `${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}`,
1135
- // );
1136
937
  clusters.push({
1137
938
  endpoint: childEndpoint.number.toString(),
1138
939
  number: childEndpoint.number,
@@ -1149,13 +950,6 @@ export class Frontend extends EventEmitter {
1149
950
  });
1150
951
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
1151
952
  }
1152
- /**
1153
- * Handles incoming websocket api request messages from the Matterbridge frontend.
1154
- *
1155
- * @param {WebSocket} client - The websocket client that sent the message.
1156
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1157
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1158
- */
1159
953
  async wsMessageHandler(client, message) {
1160
954
  let data;
1161
955
  const sendResponse = (data) => {
@@ -1175,7 +969,7 @@ export class Frontend extends EventEmitter {
1175
969
  };
1176
970
  try {
1177
971
  data = JSON.parse(message.toString());
1178
- if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
972
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1179
973
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1180
974
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1181
975
  return;
@@ -1204,111 +998,24 @@ export class Frontend extends EventEmitter {
1204
998
  }
1205
999
  }
1206
1000
  else if (data.method === '/api/install') {
1207
- const localData = data;
1208
- if (!isValidString(data.params.packageName, 10) || !isValidBoolean(data.params.restart)) {
1001
+ if (isValidString(data.params.packageName, 14) && isValidBoolean(data.params.restart)) {
1002
+ this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
1003
+ this.server.request({ type: 'plugins_install', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
1004
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1005
+ }
1006
+ else {
1209
1007
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/install' });
1210
- return;
1211
1008
  }
1212
- this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
1213
- const { spawnCommand } = await import('./utils/spawn.js');
1214
- spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'], data.params.packageName)
1215
- .then((_response) => {
1216
- sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
1217
- this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
1218
- this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
1219
- const packageName = localData.params.packageName.replace(/@.*$/, '');
1220
- if (localData.params.restart === false && packageName !== 'matterbridge') {
1221
- // The install comes from InstallPlugins
1222
- this.matterbridge.plugins
1223
- .add(packageName)
1224
- .then((plugin) => {
1225
- // istanbul ignore next if
1226
- if (plugin) {
1227
- // The plugin is not registered
1228
- this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1229
- // In childbridge mode the plugins server node is not started when added
1230
- if (this.matterbridge.bridgeMode === 'childbridge')
1231
- this.wssSendRestartRequired(true, true);
1232
- this.matterbridge.plugins
1233
- .load(plugin, true, 'The plugin has been added', true)
1234
- // eslint-disable-next-line promise/no-nesting
1235
- .then(() => {
1236
- this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
1237
- this.wssSendRefreshRequired('plugins');
1238
- this.wssSendRefreshRequired('devices');
1239
- return;
1240
- })
1241
- // eslint-disable-next-line promise/no-nesting
1242
- .catch((_error) => {
1243
- //
1244
- });
1245
- }
1246
- else {
1247
- // The plugin is already registered
1248
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1249
- this.wssSendRefreshRequired('plugins');
1250
- this.wssSendRestartRequired(true, true);
1251
- }
1252
- return;
1253
- })
1254
- // eslint-disable-next-line promise/no-nesting
1255
- .catch((_error) => {
1256
- //
1257
- });
1258
- }
1259
- else {
1260
- // The package is matterbridge
1261
- // istanbul ignore next
1262
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
1263
- // istanbul ignore next if
1264
- if (this.matterbridge.restartMode !== '') {
1265
- this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1266
- this.matterbridge.shutdownProcess();
1267
- }
1268
- else {
1269
- this.wssSendRestartRequired(true, true);
1270
- }
1271
- }
1272
- return;
1273
- })
1274
- .catch((error) => {
1275
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error });
1276
- this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
1277
- this.wssSendSnackbarMessage(`Package ${localData.params.packageName} not installed`, 10, 'error');
1278
- });
1279
1009
  }
1280
1010
  else if (data.method === '/api/uninstall') {
1281
- const localData = data;
1282
- if (!isValidString(data.params.packageName, 10)) {
1011
+ if (isValidString(data.params.packageName, 14)) {
1012
+ this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1013
+ this.server.request({ type: 'plugins_uninstall', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
1014
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1015
+ }
1016
+ else {
1283
1017
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
1284
- return;
1285
1018
  }
1286
- // The package is a plugin
1287
- const plugin = this.matterbridge.plugins.get(data.params.packageName);
1288
- // istanbul ignore next if
1289
- if (plugin) {
1290
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1291
- await this.matterbridge.plugins.remove(data.params.packageName);
1292
- this.wssSendSnackbarMessage(`Removed plugin ${data.params.packageName}`, 5, 'success');
1293
- this.wssSendRefreshRequired('plugins');
1294
- this.wssSendRefreshRequired('devices');
1295
- }
1296
- // Uninstall the package
1297
- this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1298
- const { spawnCommand } = await import('./utils/spawn.js');
1299
- spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
1300
- .then((_response) => {
1301
- sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
1302
- this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
1303
- this.wssSendSnackbarMessage(`Uninstalled package ${localData.params.packageName}`, 5, 'success');
1304
- return;
1305
- })
1306
- .catch((error) => {
1307
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error });
1308
- this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
1309
- this.wssSendSnackbarMessage(`Package ${localData.params.packageName} not uninstalled`, 10, 'error');
1310
- this.wssSendSnackbarMessage(`Restart required`, 0);
1311
- });
1312
1019
  }
1313
1020
  else if (data.method === '/api/addplugin') {
1314
1021
  const localData = data;
@@ -1336,7 +1043,6 @@ export class Frontend extends EventEmitter {
1336
1043
  return;
1337
1044
  })
1338
1045
  .catch((_error) => {
1339
- //
1340
1046
  });
1341
1047
  }
1342
1048
  else {
@@ -1384,7 +1090,6 @@ export class Frontend extends EventEmitter {
1384
1090
  return;
1385
1091
  })
1386
1092
  .catch((_error) => {
1387
- //
1388
1093
  });
1389
1094
  }
1390
1095
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1410,7 +1115,6 @@ export class Frontend extends EventEmitter {
1410
1115
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1411
1116
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1412
1117
  if (plugin.serverNode) {
1413
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1414
1118
  await this.matterbridge.stopServerNode(plugin.serverNode);
1415
1119
  plugin.serverNode = undefined;
1416
1120
  }
@@ -1420,20 +1124,18 @@ export class Frontend extends EventEmitter {
1420
1124
  this.matterbridge.devices.remove(device);
1421
1125
  }
1422
1126
  }
1423
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1424
1127
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1425
1128
  await this.matterbridge.createDynamicPlugin(plugin);
1426
1129
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1427
- plugin.restartRequired = false; // Reset plugin restartRequired
1130
+ plugin.restartRequired = false;
1428
1131
  let needRestart = 0;
1429
1132
  for (const plugin of this.matterbridge.plugins) {
1430
1133
  if (plugin.restartRequired)
1431
1134
  needRestart++;
1432
1135
  }
1433
1136
  if (needRestart === 0) {
1434
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1137
+ this.wssSendRestartNotRequired(true);
1435
1138
  }
1436
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1437
1139
  if (plugin.serverNode)
1438
1140
  await this.matterbridge.startServerNode(plugin.serverNode);
1439
1141
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1586,11 +1288,11 @@ export class Frontend extends EventEmitter {
1586
1288
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: await this.getApiSettings() });
1587
1289
  }
1588
1290
  else if (data.method === '/api/plugins') {
1589
- const plugins = this.getPlugins();
1291
+ const plugins = this.matterbridge.hasCleanupStarted ? [] : this.getPlugins();
1590
1292
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
1591
1293
  }
1592
1294
  else if (data.method === '/api/devices') {
1593
- const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1295
+ const devices = this.matterbridge.hasCleanupStarted ? [] : this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1594
1296
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
1595
1297
  }
1596
1298
  else if (data.method === '/api/clusters') {
@@ -1690,22 +1392,22 @@ export class Frontend extends EventEmitter {
1690
1392
  if (isValidString(data.params.value, 4)) {
1691
1393
  this.log.debug('Matterbridge logger level:', data.params.value);
1692
1394
  if (data.params.value === 'Debug') {
1693
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1395
+ await this.matterbridge.setLogLevel("debug");
1694
1396
  }
1695
1397
  else if (data.params.value === 'Info') {
1696
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1398
+ await this.matterbridge.setLogLevel("info");
1697
1399
  }
1698
1400
  else if (data.params.value === 'Notice') {
1699
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1401
+ await this.matterbridge.setLogLevel("notice");
1700
1402
  }
1701
1403
  else if (data.params.value === 'Warn') {
1702
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1404
+ await this.matterbridge.setLogLevel("warn");
1703
1405
  }
1704
1406
  else if (data.params.value === 'Error') {
1705
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1407
+ await this.matterbridge.setLogLevel("error");
1706
1408
  }
1707
1409
  else if (data.params.value === 'Fatal') {
1708
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1410
+ await this.matterbridge.setLogLevel("fatal");
1709
1411
  }
1710
1412
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1711
1413
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1714,11 +1416,10 @@ export class Frontend extends EventEmitter {
1714
1416
  case 'setmblogfile':
1715
1417
  if (isValidBoolean(data.params.value)) {
1716
1418
  this.log.debug('Matterbridge file log:', data.params.value);
1717
- this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1419
+ this.matterbridge.fileLogger = data.params.value;
1718
1420
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1719
- // Create the file logger for matterbridge
1720
1421
  if (data.params.value)
1721
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1422
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1722
1423
  else
1723
1424
  AnsiLogger.setGlobalLogfile(undefined);
1724
1425
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1745,7 +1446,6 @@ export class Frontend extends EventEmitter {
1745
1446
  else if (data.params.value === 'Fatal') {
1746
1447
  Logger.level = MatterLogLevel.FATAL;
1747
1448
  }
1748
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
1749
1449
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
1750
1450
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1751
1451
  }
@@ -1753,7 +1453,7 @@ export class Frontend extends EventEmitter {
1753
1453
  case 'setmjlogfile':
1754
1454
  if (isValidBoolean(data.params.value)) {
1755
1455
  this.log.debug('Matter file log:', data.params.value);
1756
- this.matterbridge.matterbridgeInformation.matterFileLogger = data.params.value;
1456
+ this.matterbridge.fileLogger = data.params.value;
1757
1457
  await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
1758
1458
  if (data.params.value) {
1759
1459
  this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
@@ -1768,89 +1468,92 @@ export class Frontend extends EventEmitter {
1768
1468
  if (isValidString(data.params.value)) {
1769
1469
  this.log.debug(`Matter.js mdns interface: ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1770
1470
  this.matterbridge.mdnsInterface = data.params.value === '' ? undefined : data.params.value;
1771
- this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
1772
1471
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', data.params.value);
1773
1472
  this.wssSendRestartRequired();
1774
1473
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1474
+ this.wssSendSnackbarMessage(`Mdns interface changed to ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1775
1475
  }
1776
1476
  break;
1777
1477
  case 'setipv4address':
1778
1478
  if (isValidString(data.params.value)) {
1779
1479
  this.log.debug(`Matter.js ipv4 address: ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
1780
- this.matterbridge.ipv4address = data.params.value === '' ? undefined : data.params.value;
1781
- this.matterbridge.matterbridgeInformation.matterIpv4Address = this.matterbridge.ipv4address;
1480
+ this.matterbridge.ipv4Address = data.params.value === '' ? undefined : data.params.value;
1782
1481
  await this.matterbridge.nodeContext?.set('matteripv4address', data.params.value);
1783
1482
  this.wssSendRestartRequired();
1784
1483
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1484
+ this.wssSendSnackbarMessage(`IPv4 address changed to ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
1785
1485
  }
1786
1486
  break;
1787
1487
  case 'setipv6address':
1788
1488
  if (isValidString(data.params.value)) {
1789
1489
  this.log.debug(`Matter.js ipv6 address: ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
1790
- this.matterbridge.ipv6address = data.params.value === '' ? undefined : data.params.value;
1791
- this.matterbridge.matterbridgeInformation.matterIpv6Address = this.matterbridge.ipv6address;
1490
+ this.matterbridge.ipv6Address = data.params.value === '' ? undefined : data.params.value;
1792
1491
  await this.matterbridge.nodeContext?.set('matteripv6address', data.params.value);
1793
1492
  this.wssSendRestartRequired();
1794
1493
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1494
+ this.wssSendSnackbarMessage(`IPv6 address changed to ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
1795
1495
  }
1796
1496
  break;
1797
1497
  case 'setmatterport':
1798
- // eslint-disable-next-line no-case-declarations
1799
1498
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1800
1499
  if (isValidNumber(port, 5540, 5600)) {
1801
1500
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
1802
- this.matterbridge.matterbridgeInformation.matterPort = port;
1501
+ this.matterbridge.port = port;
1803
1502
  await this.matterbridge.nodeContext?.set('matterport', port);
1804
1503
  this.wssSendRestartRequired();
1805
1504
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1505
+ this.wssSendSnackbarMessage(`Matter port changed to ${port}`);
1806
1506
  }
1807
1507
  else {
1808
1508
  this.log.debug(`Reset matter commissioning port to ${CYAN}5540${db}`);
1809
- this.matterbridge.matterbridgeInformation.matterPort = 5540;
1509
+ this.matterbridge.port = 5540;
1810
1510
  await this.matterbridge.nodeContext?.set('matterport', 5540);
1811
1511
  this.wssSendRestartRequired();
1812
1512
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning port to default 5540' });
1513
+ this.wssSendSnackbarMessage(`Matter port reset to default 5540`, undefined, 'error');
1813
1514
  }
1814
1515
  break;
1815
1516
  case 'setmatterdiscriminator':
1816
- // eslint-disable-next-line no-case-declarations
1817
1517
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1818
1518
  if (isValidNumber(discriminator, 0, 4095)) {
1819
1519
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
1820
- this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
1520
+ this.matterbridge.discriminator = discriminator;
1821
1521
  await this.matterbridge.nodeContext?.set('matterdiscriminator', discriminator);
1822
1522
  this.wssSendRestartRequired();
1823
1523
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1524
+ this.wssSendSnackbarMessage(`Matter discriminator changed to ${discriminator}`);
1824
1525
  }
1825
1526
  else {
1826
1527
  this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
1827
- this.matterbridge.matterbridgeInformation.matterDiscriminator = undefined;
1528
+ this.matterbridge.discriminator = undefined;
1828
1529
  await this.matterbridge.nodeContext?.remove('matterdiscriminator');
1829
1530
  this.wssSendRestartRequired();
1830
1531
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning discriminator to default undefined' });
1532
+ this.wssSendSnackbarMessage(`Matter discriminator reset to default`, undefined, 'error');
1831
1533
  }
1832
1534
  break;
1833
1535
  case 'setmatterpasscode':
1834
- // eslint-disable-next-line no-case-declarations
1835
1536
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1836
1537
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1837
- this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
1538
+ this.matterbridge.passcode = passcode;
1838
1539
  this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
1839
1540
  await this.matterbridge.nodeContext?.set('matterpasscode', passcode);
1840
1541
  this.wssSendRestartRequired();
1841
1542
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1543
+ this.wssSendSnackbarMessage(`Matter passcode changed to ${passcode}`);
1842
1544
  }
1843
1545
  else {
1844
1546
  this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
1845
- this.matterbridge.matterbridgeInformation.matterPasscode = undefined;
1547
+ this.matterbridge.passcode = undefined;
1846
1548
  await this.matterbridge.nodeContext?.remove('matterpasscode');
1847
1549
  this.wssSendRestartRequired();
1848
1550
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning passcode to default undefined' });
1551
+ this.wssSendSnackbarMessage(`Matter passcode reset to default`, undefined, 'error');
1849
1552
  }
1850
1553
  break;
1851
1554
  case 'setvirtualmode':
1852
1555
  if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
1853
- this.matterbridge.matterbridgeInformation.virtualMode = data.params.value;
1556
+ this.matterbridge.virtualMode = data.params.value;
1854
1557
  this.log.debug(`Set matterbridge virtual mode to ${CYAN}${data.params.value}${db}`);
1855
1558
  await this.matterbridge.nodeContext?.set('virtualmode', data.params.value);
1856
1559
  this.wssSendRestartRequired();
@@ -1875,19 +1578,15 @@ export class Frontend extends EventEmitter {
1875
1578
  return;
1876
1579
  }
1877
1580
  const config = plugin.configJson;
1878
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1879
1581
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1880
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1881
1582
  if (select === 'serial')
1882
1583
  this.log.info(`Selected device serial ${data.params.serial}`);
1883
1584
  if (select === 'name')
1884
1585
  this.log.info(`Selected device name ${data.params.name}`);
1885
1586
  if (config && select && (select === 'serial' || select === 'name')) {
1886
- // Remove postfix from the serial if it exists
1887
1587
  if (config.postfix) {
1888
1588
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1889
1589
  }
1890
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1891
1590
  if (isValidArray(config.whiteList, 1)) {
1892
1591
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1893
1592
  config.whiteList.push(data.params.serial);
@@ -1896,7 +1595,6 @@ export class Frontend extends EventEmitter {
1896
1595
  config.whiteList.push(data.params.name);
1897
1596
  }
1898
1597
  }
1899
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1900
1598
  if (isValidArray(config.blackList, 1)) {
1901
1599
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1902
1600
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1924,9 +1622,7 @@ export class Frontend extends EventEmitter {
1924
1622
  return;
1925
1623
  }
1926
1624
  const config = plugin.configJson;
1927
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1928
1625
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1929
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1930
1626
  if (select === 'serial')
1931
1627
  this.log.info(`Unselected device serial ${data.params.serial}`);
1932
1628
  if (select === 'name')
@@ -1935,7 +1631,6 @@ export class Frontend extends EventEmitter {
1935
1631
  if (config.postfix) {
1936
1632
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1937
1633
  }
1938
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1939
1634
  if (isValidArray(config.whiteList, 1)) {
1940
1635
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1941
1636
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1944,7 +1639,6 @@ export class Frontend extends EventEmitter {
1944
1639
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1945
1640
  }
1946
1641
  }
1947
- // Add the serial to the blackList
1948
1642
  if (isValidArray(config.blackList)) {
1949
1643
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1950
1644
  config.blackList.push(data.params.serial);
@@ -1967,7 +1661,6 @@ export class Frontend extends EventEmitter {
1967
1661
  }
1968
1662
  }
1969
1663
  else {
1970
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1971
1664
  const localData = data;
1972
1665
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1973
1666
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1977,44 +1670,21 @@ export class Frontend extends EventEmitter {
1977
1670
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1978
1671
  }
1979
1672
  }
1980
- /**
1981
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1982
- *
1983
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1984
- * @param {string} time - The time string of the message
1985
- * @param {string} name - The logger name of the message
1986
- * @param {string} message - The content of the message.
1987
- *
1988
- * @remarks
1989
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1990
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1991
- * The function sends the message to all connected clients.
1992
- */
1993
1673
  wssSendLogMessage(level, time, name, message) {
1994
1674
  if (!level || !time || !name || !message)
1995
1675
  return;
1996
- // Remove ANSI escape codes from the message
1997
- // eslint-disable-next-line no-control-regex
1998
1676
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1999
- // Remove leading asterisks from the message
2000
1677
  message = message.replace(/^\*+/, '');
2001
- // Replace all occurrences of \t and \n
2002
1678
  message = message.replace(/[\t\n]/g, '');
2003
- // Remove non-printable characters
2004
- // eslint-disable-next-line no-control-regex
2005
1679
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2006
- // Replace all occurrences of \" with "
2007
1680
  message = message.replace(/\\"/g, '"');
2008
- // Define the maximum allowed length for continuous characters without a space
2009
1681
  const maxContinuousLength = 100;
2010
1682
  const keepStartLength = 20;
2011
1683
  const keepEndLength = 20;
2012
- // Split the message into words
2013
1684
  if (level !== 'spawn') {
2014
1685
  message = message
2015
1686
  .split(' ')
2016
1687
  .map((word) => {
2017
- // If the word length exceeds the max continuous length, insert spaces and truncate
2018
1688
  if (word.length > maxContinuousLength) {
2019
1689
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2020
1690
  }
@@ -2022,161 +1692,60 @@ export class Frontend extends EventEmitter {
2022
1692
  })
2023
1693
  .join(' ');
2024
1694
  }
2025
- // Send the message to all connected clients
2026
1695
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2027
1696
  }
2028
- /**
2029
- * Sends a need to refresh WebSocket message to all connected clients.
2030
- *
2031
- * @param {string} changed - The changed value.
2032
- * @param {Record<string, unknown>} params - Additional parameters to send with the message.
2033
- * possible values for changed:
2034
- * - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
2035
- * - 'plugins'
2036
- * - 'devices'
2037
- * - 'matter' with param 'matter' (QRDiv component)
2038
- * @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
2039
- */
2040
1697
  wssSendRefreshRequired(changed, params) {
2041
1698
  this.log.debug('Sending a refresh required message to all connected clients');
2042
- // Send the message to all connected clients
2043
1699
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2044
1700
  }
2045
- /**
2046
- * Sends a need to restart WebSocket message to all connected clients.
2047
- *
2048
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
2049
- * @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
2050
- */
2051
1701
  wssSendRestartRequired(snackbar = true, fixed = false) {
2052
1702
  this.log.debug('Sending a restart required message to all connected clients');
2053
- this.matterbridge.matterbridgeInformation.restartRequired = true;
2054
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1703
+ this.matterbridge.restartRequired = true;
1704
+ this.matterbridge.fixedRestartRequired = fixed;
2055
1705
  if (snackbar === true)
2056
1706
  this.wssSendSnackbarMessage(`Restart required`, 0);
2057
- // Send the message to all connected clients
2058
1707
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2059
1708
  }
2060
- /**
2061
- * Sends a no need to restart WebSocket message to all connected clients.
2062
- *
2063
- * @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
2064
- */
2065
1709
  wssSendRestartNotRequired(snackbar = true) {
2066
1710
  this.log.debug('Sending a restart not required message to all connected clients');
2067
- this.matterbridge.matterbridgeInformation.restartRequired = false;
1711
+ this.matterbridge.restartRequired = false;
2068
1712
  if (snackbar === true)
2069
1713
  this.wssSendCloseSnackbarMessage(`Restart required`);
2070
- // Send the message to all connected clients
2071
1714
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2072
1715
  }
2073
- /**
2074
- * Sends a need to update WebSocket message to all connected clients.
2075
- *
2076
- * @param {boolean} devVersion - If true, the update is for a development version. Default is false.
2077
- */
2078
1716
  wssSendUpdateRequired(devVersion = false) {
2079
1717
  this.log.debug('Sending an update required message to all connected clients');
2080
- this.matterbridge.matterbridgeInformation.updateRequired = true;
2081
- // Send the message to all connected clients
1718
+ this.matterbridge.updateRequired = true;
2082
1719
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2083
1720
  }
2084
- /**
2085
- * Sends a cpu update message to all connected clients.
2086
- *
2087
- * @param {number} cpuUsage - The CPU usage percentage to send.
2088
- */
2089
1721
  wssSendCpuUpdate(cpuUsage) {
2090
1722
  if (hasParameter('debug'))
2091
1723
  this.log.debug('Sending a cpu update message to all connected clients');
2092
- // Send the message to all connected clients
2093
1724
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
2094
1725
  }
2095
- /**
2096
- * Sends a memory update message to all connected clients.
2097
- *
2098
- * @param {string} totalMemory - The total memory in bytes.
2099
- * @param {string} freeMemory - The free memory in bytes.
2100
- * @param {string} rss - The resident set size in bytes.
2101
- * @param {string} heapTotal - The total heap memory in bytes.
2102
- * @param {string} heapUsed - The used heap memory in bytes.
2103
- * @param {string} external - The external memory in bytes.
2104
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2105
- */
2106
1726
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2107
1727
  if (hasParameter('debug'))
2108
1728
  this.log.debug('Sending a memory update message to all connected clients');
2109
- // Send the message to all connected clients
2110
1729
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2111
1730
  }
2112
- /**
2113
- * Sends an uptime update message to all connected clients.
2114
- *
2115
- * @param {string} systemUptime - The system uptime in a human-readable format.
2116
- * @param {string} processUptime - The process uptime in a human-readable format.
2117
- */
2118
1731
  wssSendUptimeUpdate(systemUptime, processUptime) {
2119
1732
  if (hasParameter('debug'))
2120
1733
  this.log.debug('Sending a uptime update message to all connected clients');
2121
- // Send the message to all connected clients
2122
1734
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2123
1735
  }
2124
- /**
2125
- * Sends an open snackbar message to all connected clients.
2126
- *
2127
- * @param {string} message - The message to send.
2128
- * @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
2129
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
2130
- * possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
2131
- *
2132
- * @remarks
2133
- * If timeout is 0, the snackbar message will be displayed until closed by the user.
2134
- */
2135
1736
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2136
1737
  this.log.debug('Sending a snackbar message to all connected clients');
2137
- // Send the message to all connected clients
2138
1738
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2139
1739
  }
2140
- /**
2141
- * Sends a close snackbar message to all connected clients.
2142
- * It will close the snackbar message with the same message and timeout = 0.
2143
- *
2144
- * @param {string} message - The message to send.
2145
- */
2146
1740
  wssSendCloseSnackbarMessage(message) {
2147
1741
  this.log.debug('Sending a close snackbar message to all connected clients');
2148
- // Send the message to all connected clients
2149
1742
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2150
1743
  }
2151
- /**
2152
- * Sends an attribute update message to all connected WebSocket clients.
2153
- *
2154
- * @param {string | undefined} plugin - The name of the plugin.
2155
- * @param {string | undefined} serialNumber - The serial number of the device.
2156
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2157
- * @param {EndpointNumber} number - The endpoint number where the attribute belongs.
2158
- * @param {string} id - The endpoint id where the attribute belongs.
2159
- * @param {string} cluster - The cluster name where the attribute belongs.
2160
- * @param {string} attribute - The name of the attribute that changed.
2161
- * @param {number | string | boolean} value - The new value of the attribute.
2162
- *
2163
- * @remarks
2164
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2165
- * with the updated attribute information.
2166
- */
2167
1744
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2168
1745
  this.log.debug('Sending an attribute update message to all connected clients');
2169
- // Send the message to all connected clients
2170
1746
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2171
1747
  }
2172
- /**
2173
- * Sends a message to all connected clients.
2174
- * This is an helper function to send a broadcast message to all connected clients.
2175
- *
2176
- * @param {WsMessageBroadcast} msg - The message to send.
2177
- */
2178
1748
  wssBroadcastMessage(msg) {
2179
- // Send the message to all connected clients
2180
1749
  const stringifiedMsg = JSON.stringify(msg);
2181
1750
  if (msg.method !== 'log')
2182
1751
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -2187,4 +1756,3 @@ export class Frontend extends EventEmitter {
2187
1756
  });
2188
1757
  }
2189
1758
  }
2190
- //# sourceMappingURL=frontend.js.map