matterbridge 3.3.0 → 3.3.1-dev-20251008-e61b8db

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 +34 -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 +38 -99
  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 +233 -634
  36. package/dist/frontendTypes.js +0 -45
  37. package/dist/helpers.js +4 -57
  38. package/dist/index.js +2 -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 +156 -885
  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 +4 -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,50 +1,23 @@
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
- import { createServer } from 'node:http';
26
- import https from 'node:https';
1
+ if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
+ console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
27
3
  import os from 'node:os';
28
4
  import path from 'node:path';
29
- import { existsSync, promises as fs, unlinkSync } from 'node:fs';
30
5
  import EventEmitter from 'node:events';
31
- import { appendFile } from 'node:fs/promises';
32
- // Third-party modules
33
6
  import express from 'express';
34
7
  import WebSocket, { WebSocketServer } from 'ws';
35
8
  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
9
+ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
39
10
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
40
- import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
11
+ import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
12
+ import { PowerSource } from '@matter/main/clusters/power-source';
41
13
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
42
14
  import { CommissioningOptions } from '@matter/main/types';
43
- // Matterbridge
15
+ import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
44
16
  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';
17
+ import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
46
18
  import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
47
19
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
20
+ import { BroadcastServer } from './broadcastServer.js';
48
21
  export class Frontend extends EventEmitter {
49
22
  matterbridge;
50
23
  log;
@@ -54,11 +27,65 @@ export class Frontend extends EventEmitter {
54
27
  httpServer;
55
28
  httpsServer;
56
29
  webSocketServer;
30
+ server;
57
31
  constructor(matterbridge) {
58
32
  super();
59
33
  this.matterbridge = matterbridge;
60
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
34
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
61
35
  this.log.logNameColor = '\x1b[38;5;97m';
36
+ this.server = new BroadcastServer('plugins', this.log);
37
+ this.server.on('broadcast_message', this.msgHandler.bind(this));
38
+ }
39
+ destroy() {
40
+ this.server.close();
41
+ }
42
+ async msgHandler(msg) {
43
+ if (this.server.isWorkerRequest(msg, msg.type)) {
44
+ if (!msg.id || (msg.dst !== 'all' && msg.dst !== 'frontend'))
45
+ return;
46
+ this.log.debug(`**Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
47
+ switch (msg.type) {
48
+ case 'frontend_start':
49
+ await this.start(msg.params.port);
50
+ this.server.respond({ ...msg, id: msg.id, response: { success: true } });
51
+ break;
52
+ case 'frontend_stop':
53
+ await this.stop();
54
+ this.server.respond({ ...msg, id: msg.id, response: { success: true } });
55
+ break;
56
+ default:
57
+ this.log.warn(`Unknown broadcast request ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
58
+ }
59
+ }
60
+ if (this.server.isWorkerResponse(msg, msg.type)) {
61
+ this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
62
+ switch (msg.type) {
63
+ case 'plugins_install':
64
+ this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
65
+ if (msg.response.success) {
66
+ this.wssSendRestartRequired(true, true);
67
+ this.wssSendRefreshRequired('plugins');
68
+ this.wssSendSnackbarMessage(`Installed package ${msg.response.packageName}`, 5, 'success');
69
+ }
70
+ else {
71
+ this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not installed`, 10, 'error');
72
+ }
73
+ break;
74
+ case 'plugins_uninstall':
75
+ this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.response.packageName}...`);
76
+ if (msg.response.success) {
77
+ this.wssSendRestartRequired(true, true);
78
+ this.wssSendRefreshRequired('plugins');
79
+ this.wssSendSnackbarMessage(`Uninstalled package ${msg.response.packageName}`, 5, 'success');
80
+ }
81
+ else {
82
+ this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not uninstalled`, 10, 'error');
83
+ }
84
+ break;
85
+ default:
86
+ this.log.warn(`Unknown broadcast response ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
87
+ }
88
+ }
62
89
  }
63
90
  set logLevel(logLevel) {
64
91
  this.log.logLevel = logLevel;
@@ -66,61 +93,21 @@ export class Frontend extends EventEmitter {
66
93
  async start(port = 8283) {
67
94
  this.port = port;
68
95
  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
96
+ const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
71
97
  const upload = multer({ dest: uploadDir });
72
- // Create the express app that serves the frontend
73
98
  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
99
  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
100
  if (!hasParameter('ssl')) {
113
- // Create an HTTP server and attach the express app
101
+ const http = await import('node:http');
114
102
  try {
115
103
  this.log.debug(`Creating HTTP server...`);
116
- this.httpServer = createServer(this.expressApp);
104
+ this.httpServer = http.createServer(this.expressApp);
117
105
  }
118
106
  catch (error) {
119
107
  this.log.error(`Failed to create HTTP server: ${error}`);
120
108
  this.emit('server_error', error);
121
109
  return;
122
110
  }
123
- // Listen on the specified port
124
111
  if (hasParameter('ingress')) {
125
112
  this.httpServer.listen(this.port, '0.0.0.0', () => {
126
113
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -160,10 +147,10 @@ export class Frontend extends EventEmitter {
160
147
  let pfx;
161
148
  let passphrase;
162
149
  let httpsServerOptions = {};
163
- if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
164
- // Load the p12 certificate and the passphrase
150
+ const fs = await import('node:fs');
151
+ if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
165
152
  try {
166
- pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
153
+ pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
167
154
  this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
168
155
  }
169
156
  catch (error) {
@@ -172,8 +159,8 @@ export class Frontend extends EventEmitter {
172
159
  return;
173
160
  }
174
161
  try {
175
- passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
176
- passphrase = passphrase.trim(); // Ensure no extra characters
162
+ passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
163
+ passphrase = passphrase.trim();
177
164
  this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
178
165
  }
179
166
  catch (error) {
@@ -184,9 +171,8 @@ export class Frontend extends EventEmitter {
184
171
  httpsServerOptions = { pfx, passphrase };
185
172
  }
186
173
  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
174
  try {
189
- cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
175
+ cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
190
176
  this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
191
177
  }
192
178
  catch (error) {
@@ -195,7 +181,7 @@ export class Frontend extends EventEmitter {
195
181
  return;
196
182
  }
197
183
  try {
198
- key = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
184
+ key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
199
185
  this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}`);
200
186
  }
201
187
  catch (error) {
@@ -204,7 +190,7 @@ export class Frontend extends EventEmitter {
204
190
  return;
205
191
  }
206
192
  try {
207
- ca = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
193
+ ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
208
194
  fullChain = `${cert}\n${ca}`;
209
195
  this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')}`);
210
196
  }
@@ -214,10 +200,10 @@ export class Frontend extends EventEmitter {
214
200
  httpsServerOptions = { cert: fullChain ?? cert, key, ca };
215
201
  }
216
202
  if (hasParameter('mtls')) {
217
- httpsServerOptions.requestCert = true; // Request client certificate
218
- httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
203
+ httpsServerOptions.requestCert = true;
204
+ httpsServerOptions.rejectUnauthorized = true;
219
205
  }
220
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
206
+ const https = await import('node:https');
221
207
  try {
222
208
  this.log.debug(`Creating HTTPS server...`);
223
209
  this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
@@ -227,7 +213,6 @@ export class Frontend extends EventEmitter {
227
213
  this.emit('server_error', error);
228
214
  return;
229
215
  }
230
- // Listen on the specified port
231
216
  if (hasParameter('ingress')) {
232
217
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
233
218
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -259,19 +244,15 @@ export class Frontend extends EventEmitter {
259
244
  return;
260
245
  });
261
246
  }
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}...`);
247
+ this.log.debug(`Creating WebSocketServer...`);
266
248
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
267
249
  this.webSocketServer.on('connection', (ws, request) => {
268
250
  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 */;
251
+ let callbackLogLevel = "notice";
252
+ if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
253
+ callbackLogLevel = "info";
254
+ if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
255
+ callbackLogLevel = "debug";
275
256
  AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
276
257
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
277
258
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -293,7 +274,6 @@ export class Frontend extends EventEmitter {
293
274
  }
294
275
  });
295
276
  ws.on('error', (error) => {
296
- // istanbul ignore next
297
277
  this.log.error(`WebSocket client error: ${error}`);
298
278
  });
299
279
  });
@@ -301,13 +281,12 @@ export class Frontend extends EventEmitter {
301
281
  this.log.debug(`WebSocketServer closed`);
302
282
  });
303
283
  this.webSocketServer.on('listening', () => {
304
- this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
305
- this.emit('websocket_server_listening', wssHost);
284
+ this.log.info(`The WebSocketServer is listening`);
285
+ this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
306
286
  });
307
287
  this.webSocketServer.on('error', (ws, error) => {
308
288
  this.log.error(`WebSocketServer error: ${error}`);
309
289
  });
310
- // Subscribe to cli events
311
290
  cliEmitter.removeAllListeners();
312
291
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
313
292
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -318,8 +297,6 @@ export class Frontend extends EventEmitter {
318
297
  cliEmitter.on('cpu', (cpuUsage) => {
319
298
  this.wssSendCpuUpdate(cpuUsage);
320
299
  });
321
- // Endpoint to validate login code
322
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
323
300
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
324
301
  const { password } = req.body;
325
302
  this.log.debug('The frontend sent /api/login', password);
@@ -338,48 +315,41 @@ export class Frontend extends EventEmitter {
338
315
  this.log.warn('/api/login error wrong password');
339
316
  res.json({ valid: false });
340
317
  }
341
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
342
318
  }
343
319
  catch (error) {
344
320
  this.log.error('/api/login error getting password');
345
321
  res.json({ valid: false });
346
322
  }
347
323
  });
348
- // Endpoint to provide health check for docker
349
324
  this.expressApp.get('/health', (req, res) => {
350
325
  this.log.debug('Express received /health');
351
326
  const healthStatus = {
352
- status: 'ok', // Indicate service is healthy
353
- uptime: process.uptime(), // Server uptime in seconds
354
- timestamp: new Date().toISOString(), // Current timestamp
327
+ status: 'ok',
328
+ uptime: process.uptime(),
329
+ timestamp: new Date().toISOString(),
355
330
  };
356
331
  res.status(200).json(healthStatus);
357
332
  });
358
- // Endpoint to provide memory usage details
359
333
  this.expressApp.get('/memory', async (req, res) => {
360
334
  this.log.debug('Express received /memory');
361
- // Memory usage from process
362
335
  const memoryUsageRaw = process.memoryUsage();
363
336
  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),
337
+ rss: formatMemoryUsage(memoryUsageRaw.rss),
338
+ heapTotal: formatMemoryUsage(memoryUsageRaw.heapTotal),
339
+ heapUsed: formatMemoryUsage(memoryUsageRaw.heapUsed),
340
+ external: formatMemoryUsage(memoryUsageRaw.external),
341
+ arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
369
342
  };
370
- // V8 heap statistics
371
343
  const { default: v8 } = await import('node:v8');
372
344
  const heapStatsRaw = v8.getHeapStatistics();
373
345
  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
346
+ const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
377
347
  const heapSpaces = heapSpacesRaw.map((space) => ({
378
348
  ...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),
349
+ space_size: formatMemoryUsage(space.space_size),
350
+ space_used_size: formatMemoryUsage(space.space_used_size),
351
+ space_available_size: formatMemoryUsage(space.space_available_size),
352
+ physical_space_size: formatMemoryUsage(space.physical_space_size),
383
353
  }));
384
354
  const { createRequire } = await import('node:module');
385
355
  const require = createRequire(import.meta.url);
@@ -392,27 +362,23 @@ export class Frontend extends EventEmitter {
392
362
  };
393
363
  res.status(200).json(memoryReport);
394
364
  });
395
- // Endpoint to provide settings
396
365
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
397
366
  this.log.debug('The frontend sent /api/settings');
398
367
  res.json(await this.getApiSettings());
399
368
  });
400
- // Endpoint to provide plugins
401
369
  this.expressApp.get('/api/plugins', async (req, res) => {
402
370
  this.log.debug('The frontend sent /api/plugins');
403
- res.json(this.getPlugins());
371
+ res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
404
372
  });
405
- // Endpoint to provide devices
406
373
  this.expressApp.get('/api/devices', async (req, res) => {
407
374
  this.log.debug('The frontend sent /api/devices');
408
- const devices = this.getDevices();
409
- res.json(devices);
375
+ res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
410
376
  });
411
- // Endpoint to view the matterbridge log
412
377
  this.expressApp.get('/api/view-mblog', async (req, res) => {
413
378
  this.log.debug('The frontend sent /api/view-mblog');
414
379
  try {
415
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
380
+ const fs = await import('node:fs');
381
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
416
382
  res.type('text/plain');
417
383
  res.send(data);
418
384
  }
@@ -421,11 +387,11 @@ export class Frontend extends EventEmitter {
421
387
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
422
388
  }
423
389
  });
424
- // Endpoint to view the matter.js log
425
390
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
426
391
  this.log.debug('The frontend sent /api/view-mjlog');
427
392
  try {
428
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
393
+ const fs = await import('node:fs');
394
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
429
395
  res.type('text/plain');
430
396
  res.send(data);
431
397
  }
@@ -434,11 +400,9 @@ export class Frontend extends EventEmitter {
434
400
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
435
401
  }
436
402
  });
437
- // Endpoint to view the diagnostic.log
438
403
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
439
404
  this.log.debug('The frontend sent /api/view-diagnostic');
440
405
  const serverNodes = [];
441
- // istanbul ignore else
442
406
  if (this.matterbridge.bridgeMode === 'bridge') {
443
407
  if (this.matterbridge.serverNode)
444
408
  serverNodes.push(this.matterbridge.serverNode);
@@ -449,16 +413,16 @@ export class Frontend extends EventEmitter {
449
413
  serverNodes.push(plugin.serverNode);
450
414
  }
451
415
  }
452
- // istanbul ignore next
453
416
  for (const device of this.matterbridge.devices.array()) {
454
417
  if (device.serverNode)
455
418
  serverNodes.push(device.serverNode);
456
419
  }
457
- if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log')))
458
- unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'));
420
+ const fs = await import('node:fs');
421
+ if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log')))
422
+ fs.unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'));
459
423
  const diagnosticDestination = LogDestination({ name: 'diagnostic', level: MatterLogLevel.INFO, format: MatterLogFormat.formats.plain });
460
424
  diagnosticDestination.write = async (text, _message) => {
461
- await appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), text + '\n', { encoding: 'utf8' });
425
+ await fs.promises.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), text + '\n', { encoding: 'utf8' });
462
426
  };
463
427
  Logger.destinations.diagnostic = diagnosticDestination;
464
428
  if (!diagnosticDestination.context) {
@@ -472,24 +436,23 @@ export class Frontend extends EventEmitter {
472
436
  values: [...serverNodes],
473
437
  })));
474
438
  delete Logger.destinations.diagnostic;
475
- await wait(500); // Wait for the log to be written
439
+ await wait(500);
476
440
  try {
477
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
441
+ const fs = await import('node:fs');
442
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
478
443
  res.type('text/plain');
479
444
  res.send(data.slice(29));
480
445
  }
481
446
  catch (error) {
482
- // istanbul ignore next
483
447
  this.log.error(`Error reading diagnostic log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
484
- // istanbul ignore next
485
448
  res.status(500).send('Error reading diagnostic log file.');
486
449
  }
487
450
  });
488
- // Endpoint to view the shelly log
489
451
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
490
452
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
491
453
  try {
492
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
454
+ const fs = await import('node:fs');
455
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
493
456
  res.type('text/plain');
494
457
  res.send(data);
495
458
  }
@@ -498,149 +461,132 @@ export class Frontend extends EventEmitter {
498
461
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
499
462
  }
500
463
  });
501
- // Endpoint to download the matterbridge log
502
464
  this.expressApp.get('/api/download-mblog', async (req, res) => {
503
465
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
466
+ const fs = await import('node:fs');
504
467
  try {
505
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
506
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
507
- await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
468
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
469
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
470
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
508
471
  }
509
472
  catch (error) {
510
- await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
473
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
511
474
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
512
475
  }
513
476
  res.type('text/plain');
514
477
  res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
515
- /* istanbul ignore if */
516
478
  if (error) {
517
479
  this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
518
480
  res.status(500).send('Error downloading the matterbridge log file');
519
481
  }
520
482
  });
521
483
  });
522
- // Endpoint to download the matter log
523
484
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
524
485
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
486
+ const fs = await import('node:fs');
525
487
  try {
526
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
527
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
528
- await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
488
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
489
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
490
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
529
491
  }
530
492
  catch (error) {
531
- await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
493
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
532
494
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
533
495
  }
534
496
  res.type('text/plain');
535
497
  res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
536
- /* istanbul ignore if */
537
498
  if (error) {
538
499
  this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
539
500
  res.status(500).send('Error downloading the matter log file');
540
501
  }
541
502
  });
542
503
  });
543
- // Endpoint to download the shelly log
544
504
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
545
505
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
506
+ const fs = await import('node:fs');
546
507
  try {
547
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
548
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
549
- await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
508
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
509
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
510
+ await fs.promises.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
550
511
  }
551
512
  catch (error) {
552
- await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), 'Create the Shelly system log before downloading it.', 'utf-8');
513
+ await fs.promises.writeFile(path.join(os.tmpdir(), 'shelly.log'), 'Create the Shelly system log before downloading it.', 'utf-8');
553
514
  this.log.debug(`Error in /api/shellydownloadsystemlog: ${error instanceof Error ? error.message : error}`);
554
515
  }
555
516
  res.type('text/plain');
556
517
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
557
- /* istanbul ignore if */
558
518
  if (error) {
559
519
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
560
520
  res.status(500).send('Error downloading Shelly system log file');
561
521
  }
562
522
  });
563
523
  });
564
- // Endpoint to download the matterbridge storage directory
565
524
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
566
525
  this.log.debug('The frontend sent /api/download-mbstorage');
567
526
  await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
568
527
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
569
- /* istanbul ignore if */
570
528
  if (error) {
571
529
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
572
530
  res.status(500).send('Error downloading the matterbridge storage file');
573
531
  }
574
532
  });
575
533
  });
576
- // Endpoint to download the matter storage file
577
534
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
578
535
  this.log.debug('The frontend sent /api/download-mjstorage');
579
536
  await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
580
537
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
581
- /* istanbul ignore if */
582
538
  if (error) {
583
539
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
584
540
  res.status(500).send('Error downloading the matter storage zip file');
585
541
  }
586
542
  });
587
543
  });
588
- // Endpoint to download the matterbridge plugin directory
589
544
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
590
545
  this.log.debug('The frontend sent /api/download-pluginstorage');
591
546
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
592
547
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
593
- /* istanbul ignore if */
594
548
  if (error) {
595
549
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
596
550
  res.status(500).send('Error downloading the matterbridge plugin storage file');
597
551
  }
598
552
  });
599
553
  });
600
- // Endpoint to download the matterbridge plugin config files
601
554
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
602
555
  this.log.debug('The frontend sent /api/download-pluginconfig');
603
556
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
604
557
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
605
- /* istanbul ignore if */
606
558
  if (error) {
607
559
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
608
560
  res.status(500).send('Error downloading the matterbridge plugin config file');
609
561
  }
610
562
  });
611
563
  });
612
- // Endpoint to download the matterbridge backup (created with the backup command)
613
564
  this.expressApp.get('/api/download-backup', async (req, res) => {
614
565
  this.log.debug('The frontend sent /api/download-backup');
615
566
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
616
- /* istanbul ignore if */
617
567
  if (error) {
618
568
  this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
619
569
  res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
620
570
  }
621
571
  });
622
572
  });
623
- // Endpoint to upload a package
624
573
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
625
574
  const { filename } = req.body;
626
575
  const file = req.file;
627
- /* istanbul ignore if */
628
576
  if (!file || !filename) {
629
577
  this.log.error(`uploadpackage: invalid request: file and filename are required`);
630
578
  res.status(400).send('Invalid request: file and filename are required');
631
579
  return;
632
580
  }
633
581
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
634
- // Define the path where the plugin file will be saved
635
582
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
636
583
  try {
637
- // Move the uploaded file to the specified path
638
- await fs.rename(file.path, filePath);
584
+ const fs = await import('node:fs');
585
+ await fs.promises.rename(file.path, filePath);
639
586
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
640
- // Install the plugin package
641
587
  if (filename.endsWith('.tgz')) {
642
588
  const { spawnCommand } = await import('./utils/spawn.js');
643
- await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
589
+ await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
644
590
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
645
591
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
646
592
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
@@ -657,7 +603,6 @@ export class Frontend extends EventEmitter {
657
603
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
658
604
  }
659
605
  });
660
- // Fallback for routing (must be the last route)
661
606
  this.expressApp.use((req, res) => {
662
607
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
663
608
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -666,16 +611,13 @@ export class Frontend extends EventEmitter {
666
611
  }
667
612
  async stop() {
668
613
  this.log.debug('Stopping the frontend...');
669
- // Remove listeners from the express app
670
614
  if (this.expressApp) {
671
615
  this.expressApp.removeAllListeners();
672
616
  this.expressApp = undefined;
673
617
  this.log.debug('Frontend app closed successfully');
674
618
  }
675
- // Close the WebSocket server
676
619
  if (this.webSocketServer) {
677
620
  this.log.debug('Closing WebSocket server...');
678
- // Close all active connections
679
621
  this.webSocketServer.clients.forEach((client) => {
680
622
  if (client.readyState === WebSocket.OPEN) {
681
623
  client.close();
@@ -684,7 +626,6 @@ export class Frontend extends EventEmitter {
684
626
  await withTimeout(new Promise((resolve) => {
685
627
  this.webSocketServer?.close((error) => {
686
628
  if (error) {
687
- // istanbul ignore next
688
629
  this.log.error(`Error closing WebSocket server: ${error}`);
689
630
  }
690
631
  else {
@@ -697,27 +638,8 @@ export class Frontend extends EventEmitter {
697
638
  this.webSocketServer.removeAllListeners();
698
639
  this.webSocketServer = undefined;
699
640
  }
700
- // Close the http server
701
641
  if (this.httpServer) {
702
642
  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
643
  this.httpServer.close();
722
644
  this.log.debug('Http server closed successfully');
723
645
  this.listening = false;
@@ -726,27 +648,8 @@ export class Frontend extends EventEmitter {
726
648
  this.httpServer = undefined;
727
649
  this.log.debug('Frontend http server closed successfully');
728
650
  }
729
- // Close the https server
730
651
  if (this.httpsServer) {
731
652
  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
653
  this.httpsServer.close();
751
654
  this.log.debug('Https server closed successfully');
752
655
  this.listening = false;
@@ -757,70 +660,53 @@ export class Frontend extends EventEmitter {
757
660
  }
758
661
  this.log.debug('Frontend stopped successfully');
759
662
  }
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
663
  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()));
664
+ this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
665
+ this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
666
+ this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
667
+ this.matterbridge.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
799
668
  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 };
669
+ this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
670
+ this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
671
+ this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
672
+ const info = {
673
+ homeDirectory: this.matterbridge.homeDirectory,
674
+ rootDirectory: this.matterbridge.rootDirectory,
675
+ matterbridgeDirectory: this.matterbridge.matterbridgeDirectory,
676
+ matterbridgePluginDirectory: this.matterbridge.matterbridgePluginDirectory,
677
+ matterbridgeCertDirectory: this.matterbridge.matterbridgeCertDirectory,
678
+ globalModulesDirectory: this.matterbridge.globalModulesDirectory,
679
+ matterbridgeVersion: this.matterbridge.matterbridgeVersion,
680
+ matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
681
+ matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
682
+ frontendVersion: this.matterbridge.frontendVersion,
683
+ bridgeMode: this.matterbridge.bridgeMode,
684
+ restartMode: this.matterbridge.restartMode,
685
+ virtualMode: this.matterbridge.virtualMode,
686
+ profile: this.matterbridge.profile,
687
+ readOnly: this.matterbridge.readOnly,
688
+ shellyBoard: this.matterbridge.shellyBoard,
689
+ shellySysUpdate: this.matterbridge.shellySysUpdate,
690
+ shellyMainUpdate: this.matterbridge.shellyMainUpdate,
691
+ loggerLevel: await this.matterbridge.getLogLevel(),
692
+ fileLogger: this.matterbridge.fileLogger,
693
+ matterLoggerLevel: Logger.level,
694
+ matterFileLogger: this.matterbridge.matterFileLogger,
695
+ matterMdnsInterface: this.matterbridge.mdnsInterface,
696
+ matterIpv4Address: this.matterbridge.ipv4Address,
697
+ matterIpv6Address: this.matterbridge.ipv6Address,
698
+ matterPort: (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540,
699
+ matterDiscriminator: await this.matterbridge.nodeContext?.get('matterdiscriminator'),
700
+ matterPasscode: await this.matterbridge.nodeContext?.get('matterpasscode'),
701
+ restartRequired: this.matterbridge.restartRequired,
702
+ fixedRestartRequired: this.matterbridge.fixedRestartRequired,
703
+ updateRequired: this.matterbridge.updateRequired,
704
+ };
705
+ return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
816
706
  }
817
- /**
818
- * Retrieves the reachable attribute.
819
- *
820
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
821
- * @returns {boolean} The reachable attribute.
822
- */
823
707
  getReachability(device) {
708
+ if (this.matterbridge.hasCleanupStarted)
709
+ return false;
824
710
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
825
711
  return false;
826
712
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
@@ -831,13 +717,9 @@ export class Frontend extends EventEmitter {
831
717
  return true;
832
718
  return false;
833
719
  }
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
720
  getPowerSource(endpoint) {
721
+ if (this.matterbridge.hasCleanupStarted)
722
+ return;
841
723
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
842
724
  return undefined;
843
725
  const powerSource = (device) => {
@@ -852,23 +734,16 @@ export class Frontend extends EventEmitter {
852
734
  }
853
735
  return;
854
736
  };
855
- // Root endpoint
856
737
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
857
738
  return powerSource(endpoint);
858
- // Child endpoints
859
739
  for (const child of endpoint.getChildEndpoints()) {
860
740
  if (child.hasClusterServer(PowerSource.Cluster.id))
861
741
  return powerSource(child);
862
742
  }
863
743
  }
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
744
  getClusterTextFromDevice(device) {
745
+ if (this.matterbridge.hasCleanupStarted)
746
+ return '';
872
747
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
873
748
  return '';
874
749
  const getUserLabel = (device) => {
@@ -878,7 +753,6 @@ export class Frontend extends EventEmitter {
878
753
  if (composed)
879
754
  return 'Composed: ' + composed.value;
880
755
  }
881
- // istanbul ignore next cause is not reachable
882
756
  return '';
883
757
  };
884
758
  const getFixedLabel = (device) => {
@@ -888,13 +762,11 @@ export class Frontend extends EventEmitter {
888
762
  if (composed)
889
763
  return 'Composed: ' + composed.value;
890
764
  }
891
- // istanbul ignore next cause is not reacheable
892
765
  return '';
893
766
  };
894
767
  let attributes = '';
895
768
  let supportedModes = [];
896
769
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
897
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
898
770
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
899
771
  return;
900
772
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -984,20 +856,14 @@ export class Frontend extends EventEmitter {
984
856
  if (clusterName === 'userLabel' && attributeName === 'labelList')
985
857
  attributes += `${getUserLabel(device)} `;
986
858
  });
987
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
988
859
  return attributes.trimStart().trimEnd();
989
860
  }
990
- /**
991
- * Retrieves the registered plugins sanitized for res.json().
992
- *
993
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
994
- */
995
861
  getPlugins() {
996
862
  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({
863
+ return [];
864
+ const plugins = [];
865
+ for (const plugin of this.matterbridge.plugins.array()) {
866
+ plugins.push({
1001
867
  path: plugin.path,
1002
868
  type: plugin.type,
1003
869
  name: plugin.name,
@@ -1022,27 +888,18 @@ export class Frontend extends EventEmitter {
1022
888
  schemaJson: plugin.schemaJson,
1023
889
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1024
890
  hasBlackList: plugin.configJson?.blackList !== undefined,
1025
- // Childbridge mode specific data
1026
891
  matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1027
892
  });
1028
893
  }
1029
- return baseRegisteredPlugins;
894
+ return plugins;
1030
895
  }
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
896
  getDevices(pluginName) {
1038
897
  if (this.matterbridge.hasCleanupStarted)
1039
- return []; // Skip if cleanup has started
898
+ return [];
1040
899
  const devices = [];
1041
900
  for (const device of this.matterbridge.devices.array()) {
1042
- // Filter by pluginName if provided
1043
901
  if (pluginName && pluginName !== device.plugin)
1044
902
  continue;
1045
- // Check if the device has the required properties
1046
903
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1047
904
  continue;
1048
905
  devices.push({
@@ -1062,39 +919,24 @@ export class Frontend extends EventEmitter {
1062
919
  }
1063
920
  return devices;
1064
921
  }
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
922
  getClusters(pluginName, endpointNumber) {
1075
923
  if (this.matterbridge.hasCleanupStarted)
1076
- return; // Skip if cleanup has started
924
+ return;
1077
925
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1078
926
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1079
927
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1080
928
  return;
1081
929
  }
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
930
  const deviceTypes = [];
1085
931
  const clusters = [];
1086
932
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1087
933
  deviceTypes.push(d.deviceType);
1088
934
  });
1089
- // Get the clusters from the main endpoint
1090
935
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1091
936
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1092
937
  return;
1093
938
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1094
939
  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
940
  clusters.push({
1099
941
  endpoint: endpoint.number.toString(),
1100
942
  number: endpoint.number,
@@ -1108,19 +950,12 @@ export class Frontend extends EventEmitter {
1108
950
  attributeLocalValue: attributeValue,
1109
951
  });
1110
952
  });
1111
- // Get the child endpoints
1112
953
  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
954
  childEndpoints.forEach((childEndpoint) => {
1117
- // istanbul ignore if cause is not reachable: should never happen but ...
1118
955
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1119
956
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1120
957
  return;
1121
958
  }
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
959
  const deviceTypes = [];
1125
960
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1126
961
  deviceTypes.push(d.deviceType);
@@ -1130,9 +965,6 @@ export class Frontend extends EventEmitter {
1130
965
  return;
1131
966
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1132
967
  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
968
  clusters.push({
1137
969
  endpoint: childEndpoint.number.toString(),
1138
970
  number: childEndpoint.number,
@@ -1149,13 +981,6 @@ export class Frontend extends EventEmitter {
1149
981
  });
1150
982
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
1151
983
  }
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
984
  async wsMessageHandler(client, message) {
1160
985
  let data;
1161
986
  const sendResponse = (data) => {
@@ -1175,7 +1000,7 @@ export class Frontend extends EventEmitter {
1175
1000
  };
1176
1001
  try {
1177
1002
  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') {
1003
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1179
1004
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1180
1005
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1181
1006
  return;
@@ -1204,111 +1029,24 @@ export class Frontend extends EventEmitter {
1204
1029
  }
1205
1030
  }
1206
1031
  else if (data.method === '/api/install') {
1207
- const localData = data;
1208
- if (!isValidString(data.params.packageName, 10) || !isValidBoolean(data.params.restart)) {
1032
+ if (isValidString(data.params.packageName, 14) && isValidBoolean(data.params.restart)) {
1033
+ this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
1034
+ this.server.request({ type: 'plugins_install', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
1035
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1036
+ }
1037
+ else {
1209
1038
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/install' });
1210
- return;
1211
1039
  }
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
1040
  }
1280
1041
  else if (data.method === '/api/uninstall') {
1281
- const localData = data;
1282
- if (!isValidString(data.params.packageName, 10)) {
1042
+ if (isValidString(data.params.packageName, 14)) {
1043
+ this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1044
+ this.server.request({ type: 'plugins_uninstall', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
1045
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1046
+ }
1047
+ else {
1283
1048
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
1284
- return;
1285
1049
  }
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
1050
  }
1313
1051
  else if (data.method === '/api/addplugin') {
1314
1052
  const localData = data;
@@ -1336,7 +1074,6 @@ export class Frontend extends EventEmitter {
1336
1074
  return;
1337
1075
  })
1338
1076
  .catch((_error) => {
1339
- //
1340
1077
  });
1341
1078
  }
1342
1079
  else {
@@ -1384,7 +1121,6 @@ export class Frontend extends EventEmitter {
1384
1121
  return;
1385
1122
  })
1386
1123
  .catch((_error) => {
1387
- //
1388
1124
  });
1389
1125
  }
1390
1126
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1410,7 +1146,6 @@ export class Frontend extends EventEmitter {
1410
1146
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1411
1147
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
1412
1148
  if (plugin.serverNode) {
1413
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1414
1149
  await this.matterbridge.stopServerNode(plugin.serverNode);
1415
1150
  plugin.serverNode = undefined;
1416
1151
  }
@@ -1420,20 +1155,18 @@ export class Frontend extends EventEmitter {
1420
1155
  this.matterbridge.devices.remove(device);
1421
1156
  }
1422
1157
  }
1423
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1424
1158
  if (plugin.type === 'DynamicPlatform' && !plugin.locked)
1425
1159
  await this.matterbridge.createDynamicPlugin(plugin);
1426
1160
  await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
1427
- plugin.restartRequired = false; // Reset plugin restartRequired
1161
+ plugin.restartRequired = false;
1428
1162
  let needRestart = 0;
1429
1163
  for (const plugin of this.matterbridge.plugins) {
1430
1164
  if (plugin.restartRequired)
1431
1165
  needRestart++;
1432
1166
  }
1433
1167
  if (needRestart === 0) {
1434
- this.wssSendRestartNotRequired(true); // Reset global restart required message
1168
+ this.wssSendRestartNotRequired(true);
1435
1169
  }
1436
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1437
1170
  if (plugin.serverNode)
1438
1171
  await this.matterbridge.startServerNode(plugin.serverNode);
1439
1172
  this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
@@ -1586,11 +1319,11 @@ export class Frontend extends EventEmitter {
1586
1319
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: await this.getApiSettings() });
1587
1320
  }
1588
1321
  else if (data.method === '/api/plugins') {
1589
- const plugins = this.getPlugins();
1322
+ const plugins = this.matterbridge.hasCleanupStarted ? [] : this.getPlugins();
1590
1323
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
1591
1324
  }
1592
1325
  else if (data.method === '/api/devices') {
1593
- const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1326
+ const devices = this.matterbridge.hasCleanupStarted ? [] : this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1594
1327
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
1595
1328
  }
1596
1329
  else if (data.method === '/api/clusters') {
@@ -1690,22 +1423,22 @@ export class Frontend extends EventEmitter {
1690
1423
  if (isValidString(data.params.value, 4)) {
1691
1424
  this.log.debug('Matterbridge logger level:', data.params.value);
1692
1425
  if (data.params.value === 'Debug') {
1693
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1426
+ await this.matterbridge.setLogLevel("debug");
1694
1427
  }
1695
1428
  else if (data.params.value === 'Info') {
1696
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1429
+ await this.matterbridge.setLogLevel("info");
1697
1430
  }
1698
1431
  else if (data.params.value === 'Notice') {
1699
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1432
+ await this.matterbridge.setLogLevel("notice");
1700
1433
  }
1701
1434
  else if (data.params.value === 'Warn') {
1702
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1435
+ await this.matterbridge.setLogLevel("warn");
1703
1436
  }
1704
1437
  else if (data.params.value === 'Error') {
1705
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1438
+ await this.matterbridge.setLogLevel("error");
1706
1439
  }
1707
1440
  else if (data.params.value === 'Fatal') {
1708
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1441
+ await this.matterbridge.setLogLevel("fatal");
1709
1442
  }
1710
1443
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1711
1444
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1714,11 +1447,10 @@ export class Frontend extends EventEmitter {
1714
1447
  case 'setmblogfile':
1715
1448
  if (isValidBoolean(data.params.value)) {
1716
1449
  this.log.debug('Matterbridge file log:', data.params.value);
1717
- this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1450
+ this.matterbridge.fileLogger = data.params.value;
1718
1451
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1719
- // Create the file logger for matterbridge
1720
1452
  if (data.params.value)
1721
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1453
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
1722
1454
  else
1723
1455
  AnsiLogger.setGlobalLogfile(undefined);
1724
1456
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
@@ -1745,7 +1477,6 @@ export class Frontend extends EventEmitter {
1745
1477
  else if (data.params.value === 'Fatal') {
1746
1478
  Logger.level = MatterLogLevel.FATAL;
1747
1479
  }
1748
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
1749
1480
  await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
1750
1481
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1751
1482
  }
@@ -1753,7 +1484,7 @@ export class Frontend extends EventEmitter {
1753
1484
  case 'setmjlogfile':
1754
1485
  if (isValidBoolean(data.params.value)) {
1755
1486
  this.log.debug('Matter file log:', data.params.value);
1756
- this.matterbridge.matterbridgeInformation.matterFileLogger = data.params.value;
1487
+ this.matterbridge.matterFileLogger = data.params.value;
1757
1488
  await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
1758
1489
  if (data.params.value) {
1759
1490
  this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
@@ -1768,89 +1499,92 @@ export class Frontend extends EventEmitter {
1768
1499
  if (isValidString(data.params.value)) {
1769
1500
  this.log.debug(`Matter.js mdns interface: ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1770
1501
  this.matterbridge.mdnsInterface = data.params.value === '' ? undefined : data.params.value;
1771
- this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
1772
1502
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', data.params.value);
1773
1503
  this.wssSendRestartRequired();
1774
1504
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1505
+ this.wssSendSnackbarMessage(`Mdns interface changed to ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1775
1506
  }
1776
1507
  break;
1777
1508
  case 'setipv4address':
1778
1509
  if (isValidString(data.params.value)) {
1779
1510
  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;
1511
+ this.matterbridge.ipv4Address = data.params.value === '' ? undefined : data.params.value;
1782
1512
  await this.matterbridge.nodeContext?.set('matteripv4address', data.params.value);
1783
1513
  this.wssSendRestartRequired();
1784
1514
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1515
+ this.wssSendSnackbarMessage(`IPv4 address changed to ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
1785
1516
  }
1786
1517
  break;
1787
1518
  case 'setipv6address':
1788
1519
  if (isValidString(data.params.value)) {
1789
1520
  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;
1521
+ this.matterbridge.ipv6Address = data.params.value === '' ? undefined : data.params.value;
1792
1522
  await this.matterbridge.nodeContext?.set('matteripv6address', data.params.value);
1793
1523
  this.wssSendRestartRequired();
1794
1524
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1525
+ this.wssSendSnackbarMessage(`IPv6 address changed to ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
1795
1526
  }
1796
1527
  break;
1797
1528
  case 'setmatterport':
1798
- // eslint-disable-next-line no-case-declarations
1799
1529
  const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1800
1530
  if (isValidNumber(port, 5540, 5600)) {
1801
1531
  this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
1802
- this.matterbridge.matterbridgeInformation.matterPort = port;
1532
+ this.matterbridge.port = port;
1803
1533
  await this.matterbridge.nodeContext?.set('matterport', port);
1804
1534
  this.wssSendRestartRequired();
1805
1535
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1536
+ this.wssSendSnackbarMessage(`Matter port changed to ${port}`);
1806
1537
  }
1807
1538
  else {
1808
1539
  this.log.debug(`Reset matter commissioning port to ${CYAN}5540${db}`);
1809
- this.matterbridge.matterbridgeInformation.matterPort = 5540;
1540
+ this.matterbridge.port = 5540;
1810
1541
  await this.matterbridge.nodeContext?.set('matterport', 5540);
1811
1542
  this.wssSendRestartRequired();
1812
1543
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning port to default 5540' });
1544
+ this.wssSendSnackbarMessage(`Matter port reset to default 5540`, undefined, 'error');
1813
1545
  }
1814
1546
  break;
1815
1547
  case 'setmatterdiscriminator':
1816
- // eslint-disable-next-line no-case-declarations
1817
1548
  const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1818
1549
  if (isValidNumber(discriminator, 0, 4095)) {
1819
1550
  this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
1820
- this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
1551
+ this.matterbridge.discriminator = discriminator;
1821
1552
  await this.matterbridge.nodeContext?.set('matterdiscriminator', discriminator);
1822
1553
  this.wssSendRestartRequired();
1823
1554
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1555
+ this.wssSendSnackbarMessage(`Matter discriminator changed to ${discriminator}`);
1824
1556
  }
1825
1557
  else {
1826
1558
  this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
1827
- this.matterbridge.matterbridgeInformation.matterDiscriminator = undefined;
1559
+ this.matterbridge.discriminator = undefined;
1828
1560
  await this.matterbridge.nodeContext?.remove('matterdiscriminator');
1829
1561
  this.wssSendRestartRequired();
1830
1562
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning discriminator to default undefined' });
1563
+ this.wssSendSnackbarMessage(`Matter discriminator reset to default`, undefined, 'error');
1831
1564
  }
1832
1565
  break;
1833
1566
  case 'setmatterpasscode':
1834
- // eslint-disable-next-line no-case-declarations
1835
1567
  const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1836
1568
  if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
1837
- this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
1569
+ this.matterbridge.passcode = passcode;
1838
1570
  this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
1839
1571
  await this.matterbridge.nodeContext?.set('matterpasscode', passcode);
1840
1572
  this.wssSendRestartRequired();
1841
1573
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1574
+ this.wssSendSnackbarMessage(`Matter passcode changed to ${passcode}`);
1842
1575
  }
1843
1576
  else {
1844
1577
  this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
1845
- this.matterbridge.matterbridgeInformation.matterPasscode = undefined;
1578
+ this.matterbridge.passcode = undefined;
1846
1579
  await this.matterbridge.nodeContext?.remove('matterpasscode');
1847
1580
  this.wssSendRestartRequired();
1848
1581
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning passcode to default undefined' });
1582
+ this.wssSendSnackbarMessage(`Matter passcode reset to default`, undefined, 'error');
1849
1583
  }
1850
1584
  break;
1851
1585
  case 'setvirtualmode':
1852
1586
  if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
1853
- this.matterbridge.matterbridgeInformation.virtualMode = data.params.value;
1587
+ this.matterbridge.virtualMode = data.params.value;
1854
1588
  this.log.debug(`Set matterbridge virtual mode to ${CYAN}${data.params.value}${db}`);
1855
1589
  await this.matterbridge.nodeContext?.set('virtualmode', data.params.value);
1856
1590
  this.wssSendRestartRequired();
@@ -1875,19 +1609,15 @@ export class Frontend extends EventEmitter {
1875
1609
  return;
1876
1610
  }
1877
1611
  const config = plugin.configJson;
1878
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1879
1612
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1880
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1881
1613
  if (select === 'serial')
1882
1614
  this.log.info(`Selected device serial ${data.params.serial}`);
1883
1615
  if (select === 'name')
1884
1616
  this.log.info(`Selected device name ${data.params.name}`);
1885
1617
  if (config && select && (select === 'serial' || select === 'name')) {
1886
- // Remove postfix from the serial if it exists
1887
1618
  if (config.postfix) {
1888
1619
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1889
1620
  }
1890
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1891
1621
  if (isValidArray(config.whiteList, 1)) {
1892
1622
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1893
1623
  config.whiteList.push(data.params.serial);
@@ -1896,7 +1626,6 @@ export class Frontend extends EventEmitter {
1896
1626
  config.whiteList.push(data.params.name);
1897
1627
  }
1898
1628
  }
1899
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1900
1629
  if (isValidArray(config.blackList, 1)) {
1901
1630
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1902
1631
  config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
@@ -1924,9 +1653,7 @@ export class Frontend extends EventEmitter {
1924
1653
  return;
1925
1654
  }
1926
1655
  const config = plugin.configJson;
1927
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1928
1656
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1929
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1930
1657
  if (select === 'serial')
1931
1658
  this.log.info(`Unselected device serial ${data.params.serial}`);
1932
1659
  if (select === 'name')
@@ -1935,7 +1662,6 @@ export class Frontend extends EventEmitter {
1935
1662
  if (config.postfix) {
1936
1663
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1937
1664
  }
1938
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1939
1665
  if (isValidArray(config.whiteList, 1)) {
1940
1666
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1941
1667
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
@@ -1944,7 +1670,6 @@ export class Frontend extends EventEmitter {
1944
1670
  config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
1945
1671
  }
1946
1672
  }
1947
- // Add the serial to the blackList
1948
1673
  if (isValidArray(config.blackList)) {
1949
1674
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1950
1675
  config.blackList.push(data.params.serial);
@@ -1967,7 +1692,6 @@ export class Frontend extends EventEmitter {
1967
1692
  }
1968
1693
  }
1969
1694
  else {
1970
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1971
1695
  const localData = data;
1972
1696
  this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
1973
1697
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
@@ -1977,44 +1701,21 @@ export class Frontend extends EventEmitter {
1977
1701
  inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
1978
1702
  }
1979
1703
  }
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
1704
  wssSendLogMessage(level, time, name, message) {
1994
1705
  if (!level || !time || !name || !message)
1995
1706
  return;
1996
- // Remove ANSI escape codes from the message
1997
- // eslint-disable-next-line no-control-regex
1998
1707
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1999
- // Remove leading asterisks from the message
2000
1708
  message = message.replace(/^\*+/, '');
2001
- // Replace all occurrences of \t and \n
2002
1709
  message = message.replace(/[\t\n]/g, '');
2003
- // Remove non-printable characters
2004
- // eslint-disable-next-line no-control-regex
2005
1710
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
2006
- // Replace all occurrences of \" with "
2007
1711
  message = message.replace(/\\"/g, '"');
2008
- // Define the maximum allowed length for continuous characters without a space
2009
1712
  const maxContinuousLength = 100;
2010
1713
  const keepStartLength = 20;
2011
1714
  const keepEndLength = 20;
2012
- // Split the message into words
2013
1715
  if (level !== 'spawn') {
2014
1716
  message = message
2015
1717
  .split(' ')
2016
1718
  .map((word) => {
2017
- // If the word length exceeds the max continuous length, insert spaces and truncate
2018
1719
  if (word.length > maxContinuousLength) {
2019
1720
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2020
1721
  }
@@ -2022,161 +1723,60 @@ export class Frontend extends EventEmitter {
2022
1723
  })
2023
1724
  .join(' ');
2024
1725
  }
2025
- // Send the message to all connected clients
2026
1726
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
2027
1727
  }
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
1728
  wssSendRefreshRequired(changed, params) {
2041
1729
  this.log.debug('Sending a refresh required message to all connected clients');
2042
- // Send the message to all connected clients
2043
1730
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
2044
1731
  }
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
1732
  wssSendRestartRequired(snackbar = true, fixed = false) {
2052
1733
  this.log.debug('Sending a restart required message to all connected clients');
2053
- this.matterbridge.matterbridgeInformation.restartRequired = true;
2054
- this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
1734
+ this.matterbridge.restartRequired = true;
1735
+ this.matterbridge.fixedRestartRequired = fixed;
2055
1736
  if (snackbar === true)
2056
1737
  this.wssSendSnackbarMessage(`Restart required`, 0);
2057
- // Send the message to all connected clients
2058
1738
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
2059
1739
  }
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
1740
  wssSendRestartNotRequired(snackbar = true) {
2066
1741
  this.log.debug('Sending a restart not required message to all connected clients');
2067
- this.matterbridge.matterbridgeInformation.restartRequired = false;
1742
+ this.matterbridge.restartRequired = false;
2068
1743
  if (snackbar === true)
2069
1744
  this.wssSendCloseSnackbarMessage(`Restart required`);
2070
- // Send the message to all connected clients
2071
1745
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
2072
1746
  }
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
1747
  wssSendUpdateRequired(devVersion = false) {
2079
1748
  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
1749
+ this.matterbridge.updateRequired = true;
2082
1750
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
2083
1751
  }
2084
- /**
2085
- * Sends a cpu update message to all connected clients.
2086
- *
2087
- * @param {number} cpuUsage - The CPU usage percentage to send.
2088
- */
2089
1752
  wssSendCpuUpdate(cpuUsage) {
2090
1753
  if (hasParameter('debug'))
2091
1754
  this.log.debug('Sending a cpu update message to all connected clients');
2092
- // Send the message to all connected clients
2093
1755
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
2094
1756
  }
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
1757
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2107
1758
  if (hasParameter('debug'))
2108
1759
  this.log.debug('Sending a memory update message to all connected clients');
2109
- // Send the message to all connected clients
2110
1760
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
2111
1761
  }
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
1762
  wssSendUptimeUpdate(systemUptime, processUptime) {
2119
1763
  if (hasParameter('debug'))
2120
1764
  this.log.debug('Sending a uptime update message to all connected clients');
2121
- // Send the message to all connected clients
2122
1765
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
2123
1766
  }
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
1767
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2136
1768
  this.log.debug('Sending a snackbar message to all connected clients');
2137
- // Send the message to all connected clients
2138
1769
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
2139
1770
  }
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
1771
  wssSendCloseSnackbarMessage(message) {
2147
1772
  this.log.debug('Sending a close snackbar message to all connected clients');
2148
- // Send the message to all connected clients
2149
1773
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
2150
1774
  }
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
1775
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
2168
1776
  this.log.debug('Sending an attribute update message to all connected clients');
2169
- // Send the message to all connected clients
2170
1777
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
2171
1778
  }
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
1779
  wssBroadcastMessage(msg) {
2179
- // Send the message to all connected clients
2180
1780
  const stringifiedMsg = JSON.stringify(msg);
2181
1781
  if (msg.method !== 'log')
2182
1782
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
@@ -2187,4 +1787,3 @@ export class Frontend extends EventEmitter {
2187
1787
  });
2188
1788
  }
2189
1789
  }
2190
- //# sourceMappingURL=frontend.js.map