matterbridge 3.1.1 → 3.1.2-dev-20250706-6c6481e

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 (208) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README-DEV.md +6 -2
  3. package/dist/cli.js +2 -91
  4. package/dist/cliEmitter.js +0 -30
  5. package/dist/clusters/export.js +0 -2
  6. package/dist/defaultConfigSchema.js +0 -24
  7. package/dist/deviceManager.js +1 -94
  8. package/dist/devices/batteryStorage.js +1 -48
  9. package/dist/devices/evse.js +10 -74
  10. package/dist/devices/export.js +0 -2
  11. package/dist/devices/heatPump.js +2 -50
  12. package/dist/devices/laundryDryer.js +6 -83
  13. package/dist/devices/laundryWasher.js +7 -91
  14. package/dist/devices/roboticVacuumCleaner.js +8 -84
  15. package/dist/devices/solarPower.js +0 -38
  16. package/dist/devices/waterHeater.js +2 -82
  17. package/dist/frontend.js +53 -474
  18. package/dist/globalMatterbridge.js +0 -47
  19. package/dist/helpers.js +0 -53
  20. package/dist/index.js +1 -39
  21. package/dist/logger/export.js +0 -1
  22. package/dist/matter/behaviors.js +0 -2
  23. package/dist/matter/clusters.js +0 -2
  24. package/dist/matter/devices.js +0 -2
  25. package/dist/matter/endpoints.js +0 -2
  26. package/dist/matter/export.js +0 -3
  27. package/dist/matter/types.js +0 -3
  28. package/dist/matterbridge.js +52 -804
  29. package/dist/matterbridgeAccessoryPlatform.js +0 -36
  30. package/dist/matterbridgeBehaviors.js +1 -61
  31. package/dist/matterbridgeDeviceTypes.js +15 -579
  32. package/dist/matterbridgeDynamicPlatform.js +0 -36
  33. package/dist/matterbridgeEndpoint.js +42 -1027
  34. package/dist/matterbridgeEndpointHelpers.js +12 -322
  35. package/dist/matterbridgePlatform.js +0 -233
  36. package/dist/matterbridgeTypes.js +0 -25
  37. package/dist/pluginManager.js +3 -269
  38. package/dist/shelly.js +7 -168
  39. package/dist/storage/export.js +0 -1
  40. package/dist/update.js +0 -54
  41. package/dist/utils/colorUtils.js +2 -263
  42. package/dist/utils/commandLine.js +0 -54
  43. package/dist/utils/copyDirectory.js +1 -38
  44. package/dist/utils/createDirectory.js +0 -33
  45. package/dist/utils/createZip.js +2 -47
  46. package/dist/utils/deepCopy.js +0 -39
  47. package/dist/utils/deepEqual.js +1 -72
  48. package/dist/utils/export.js +0 -1
  49. package/dist/utils/hex.js +0 -58
  50. package/dist/utils/isvalid.js +0 -101
  51. package/dist/utils/network.js +5 -83
  52. package/dist/utils/spawn.js +0 -18
  53. package/dist/utils/wait.js +9 -62
  54. package/npm-shrinkwrap.json +2 -2
  55. package/package.json +1 -2
  56. package/dist/cli.d.ts +0 -26
  57. package/dist/cli.d.ts.map +0 -1
  58. package/dist/cli.js.map +0 -1
  59. package/dist/cliEmitter.d.ts +0 -34
  60. package/dist/cliEmitter.d.ts.map +0 -1
  61. package/dist/cliEmitter.js.map +0 -1
  62. package/dist/clusters/export.d.ts +0 -2
  63. package/dist/clusters/export.d.ts.map +0 -1
  64. package/dist/clusters/export.js.map +0 -1
  65. package/dist/defaultConfigSchema.d.ts +0 -28
  66. package/dist/defaultConfigSchema.d.ts.map +0 -1
  67. package/dist/defaultConfigSchema.js.map +0 -1
  68. package/dist/deviceManager.d.ts +0 -112
  69. package/dist/deviceManager.d.ts.map +0 -1
  70. package/dist/deviceManager.js.map +0 -1
  71. package/dist/devices/batteryStorage.d.ts +0 -48
  72. package/dist/devices/batteryStorage.d.ts.map +0 -1
  73. package/dist/devices/batteryStorage.js.map +0 -1
  74. package/dist/devices/evse.d.ts +0 -75
  75. package/dist/devices/evse.d.ts.map +0 -1
  76. package/dist/devices/evse.js.map +0 -1
  77. package/dist/devices/export.d.ts +0 -9
  78. package/dist/devices/export.d.ts.map +0 -1
  79. package/dist/devices/export.js.map +0 -1
  80. package/dist/devices/heatPump.d.ts +0 -47
  81. package/dist/devices/heatPump.d.ts.map +0 -1
  82. package/dist/devices/heatPump.js.map +0 -1
  83. package/dist/devices/laundryDryer.d.ts +0 -87
  84. package/dist/devices/laundryDryer.d.ts.map +0 -1
  85. package/dist/devices/laundryDryer.js.map +0 -1
  86. package/dist/devices/laundryWasher.d.ts +0 -242
  87. package/dist/devices/laundryWasher.d.ts.map +0 -1
  88. package/dist/devices/laundryWasher.js.map +0 -1
  89. package/dist/devices/roboticVacuumCleaner.d.ts +0 -103
  90. package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
  91. package/dist/devices/roboticVacuumCleaner.js.map +0 -1
  92. package/dist/devices/solarPower.d.ts +0 -40
  93. package/dist/devices/solarPower.d.ts.map +0 -1
  94. package/dist/devices/solarPower.js.map +0 -1
  95. package/dist/devices/waterHeater.d.ts +0 -111
  96. package/dist/devices/waterHeater.d.ts.map +0 -1
  97. package/dist/devices/waterHeater.js.map +0 -1
  98. package/dist/frontend.d.ts +0 -302
  99. package/dist/frontend.d.ts.map +0 -1
  100. package/dist/frontend.js.map +0 -1
  101. package/dist/globalMatterbridge.d.ts +0 -59
  102. package/dist/globalMatterbridge.d.ts.map +0 -1
  103. package/dist/globalMatterbridge.js.map +0 -1
  104. package/dist/helpers.d.ts +0 -48
  105. package/dist/helpers.d.ts.map +0 -1
  106. package/dist/helpers.js.map +0 -1
  107. package/dist/index.d.ts +0 -41
  108. package/dist/index.d.ts.map +0 -1
  109. package/dist/index.js.map +0 -1
  110. package/dist/logger/export.d.ts +0 -2
  111. package/dist/logger/export.d.ts.map +0 -1
  112. package/dist/logger/export.js.map +0 -1
  113. package/dist/matter/behaviors.d.ts +0 -2
  114. package/dist/matter/behaviors.d.ts.map +0 -1
  115. package/dist/matter/behaviors.js.map +0 -1
  116. package/dist/matter/clusters.d.ts +0 -2
  117. package/dist/matter/clusters.d.ts.map +0 -1
  118. package/dist/matter/clusters.js.map +0 -1
  119. package/dist/matter/devices.d.ts +0 -2
  120. package/dist/matter/devices.d.ts.map +0 -1
  121. package/dist/matter/devices.js.map +0 -1
  122. package/dist/matter/endpoints.d.ts +0 -2
  123. package/dist/matter/endpoints.d.ts.map +0 -1
  124. package/dist/matter/endpoints.js.map +0 -1
  125. package/dist/matter/export.d.ts +0 -5
  126. package/dist/matter/export.d.ts.map +0 -1
  127. package/dist/matter/export.js.map +0 -1
  128. package/dist/matter/types.d.ts +0 -3
  129. package/dist/matter/types.d.ts.map +0 -1
  130. package/dist/matter/types.js.map +0 -1
  131. package/dist/matterbridge.d.ts +0 -450
  132. package/dist/matterbridge.d.ts.map +0 -1
  133. package/dist/matterbridge.js.map +0 -1
  134. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
  135. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  136. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  137. package/dist/matterbridgeBehaviors.d.ts +0 -1340
  138. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  139. package/dist/matterbridgeBehaviors.js.map +0 -1
  140. package/dist/matterbridgeDeviceTypes.d.ts +0 -709
  141. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  142. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  143. package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
  144. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  145. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  146. package/dist/matterbridgeEndpoint.d.ts +0 -1179
  147. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  148. package/dist/matterbridgeEndpoint.js.map +0 -1
  149. package/dist/matterbridgeEndpointHelpers.d.ts +0 -3198
  150. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  151. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  152. package/dist/matterbridgePlatform.d.ts +0 -310
  153. package/dist/matterbridgePlatform.d.ts.map +0 -1
  154. package/dist/matterbridgePlatform.js.map +0 -1
  155. package/dist/matterbridgeTypes.d.ts +0 -192
  156. package/dist/matterbridgeTypes.d.ts.map +0 -1
  157. package/dist/matterbridgeTypes.js.map +0 -1
  158. package/dist/pluginManager.d.ts +0 -291
  159. package/dist/pluginManager.d.ts.map +0 -1
  160. package/dist/pluginManager.js.map +0 -1
  161. package/dist/shelly.d.ts +0 -174
  162. package/dist/shelly.d.ts.map +0 -1
  163. package/dist/shelly.js.map +0 -1
  164. package/dist/storage/export.d.ts +0 -2
  165. package/dist/storage/export.d.ts.map +0 -1
  166. package/dist/storage/export.js.map +0 -1
  167. package/dist/update.d.ts +0 -59
  168. package/dist/update.d.ts.map +0 -1
  169. package/dist/update.js.map +0 -1
  170. package/dist/utils/colorUtils.d.ts +0 -117
  171. package/dist/utils/colorUtils.d.ts.map +0 -1
  172. package/dist/utils/colorUtils.js.map +0 -1
  173. package/dist/utils/commandLine.d.ts +0 -59
  174. package/dist/utils/commandLine.d.ts.map +0 -1
  175. package/dist/utils/commandLine.js.map +0 -1
  176. package/dist/utils/copyDirectory.d.ts +0 -33
  177. package/dist/utils/copyDirectory.d.ts.map +0 -1
  178. package/dist/utils/copyDirectory.js.map +0 -1
  179. package/dist/utils/createDirectory.d.ts +0 -34
  180. package/dist/utils/createDirectory.d.ts.map +0 -1
  181. package/dist/utils/createDirectory.js.map +0 -1
  182. package/dist/utils/createZip.d.ts +0 -39
  183. package/dist/utils/createZip.d.ts.map +0 -1
  184. package/dist/utils/createZip.js.map +0 -1
  185. package/dist/utils/deepCopy.d.ts +0 -32
  186. package/dist/utils/deepCopy.d.ts.map +0 -1
  187. package/dist/utils/deepCopy.js.map +0 -1
  188. package/dist/utils/deepEqual.d.ts +0 -54
  189. package/dist/utils/deepEqual.d.ts.map +0 -1
  190. package/dist/utils/deepEqual.js.map +0 -1
  191. package/dist/utils/export.d.ts +0 -12
  192. package/dist/utils/export.d.ts.map +0 -1
  193. package/dist/utils/export.js.map +0 -1
  194. package/dist/utils/hex.d.ts +0 -49
  195. package/dist/utils/hex.d.ts.map +0 -1
  196. package/dist/utils/hex.js.map +0 -1
  197. package/dist/utils/isvalid.d.ts +0 -103
  198. package/dist/utils/isvalid.d.ts.map +0 -1
  199. package/dist/utils/isvalid.js.map +0 -1
  200. package/dist/utils/network.d.ts +0 -76
  201. package/dist/utils/network.d.ts.map +0 -1
  202. package/dist/utils/network.js.map +0 -1
  203. package/dist/utils/spawn.d.ts +0 -11
  204. package/dist/utils/spawn.d.ts.map +0 -1
  205. package/dist/utils/spawn.js.map +0 -1
  206. package/dist/utils/wait.d.ts +0 -56
  207. package/dist/utils/wait.d.ts.map +0 -1
  208. package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js CHANGED
@@ -1,126 +1,30 @@
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.1.0
8
- * @license Apache-2.0
9
- *
10
- * Copyright 2025, 2026, 2027 Luca Liguori.
11
- *
12
- * Licensed under the Apache License, Version 2.0 (the "License");
13
- * you may not use this file except in compliance with the License.
14
- * You may obtain a copy of the License at
15
- *
16
- * http://www.apache.org/licenses/LICENSE-2.0
17
- *
18
- * Unless required by applicable law or agreed to in writing, software
19
- * distributed under the License is distributed on an "AS IS" BASIS,
20
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- * See the License for the specific language governing permissions and
22
- * limitations under the License.
23
- */
24
- // Node modules
25
1
  import { createServer } from 'node:http';
26
2
  import https from 'node:https';
27
3
  import os from 'node:os';
28
4
  import path from 'node:path';
29
5
  import { promises as fs } from 'node:fs';
30
6
  import EventEmitter from 'node:events';
31
- // Third-party modules
32
7
  import express from 'express';
33
8
  import WebSocket, { WebSocketServer } from 'ws';
34
9
  import multer from 'multer';
35
- // AnsiLogger module
36
- import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from 'node-ansi-logger';
37
- // @matter
10
+ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
38
11
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
39
12
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
40
- // Matterbridge
41
13
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
42
14
  import { plg } from './matterbridgeTypes.js';
43
- import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
15
+ import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
44
16
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
45
- /**
46
- * Websocket message ID for logging.
47
- *
48
- * @constant {number}
49
- */
50
17
  export const WS_ID_LOG = 0;
51
- /**
52
- * Websocket message ID indicating a refresh is needed.
53
- *
54
- * @constant {number}
55
- */
56
18
  export const WS_ID_REFRESH_NEEDED = 1;
57
- /**
58
- * Websocket message ID indicating a restart is needed.
59
- *
60
- * @constant {number}
61
- */
62
19
  export const WS_ID_RESTART_NEEDED = 2;
63
- /**
64
- * Websocket message ID indicating a cpu update.
65
- *
66
- * @constant {number}
67
- */
68
20
  export const WS_ID_CPU_UPDATE = 3;
69
- /**
70
- * Websocket message ID indicating a memory update.
71
- *
72
- * @constant {number}
73
- */
74
21
  export const WS_ID_MEMORY_UPDATE = 4;
75
- /**
76
- * Websocket message ID indicating an uptime update.
77
- *
78
- * @constant {number}
79
- */
80
22
  export const WS_ID_UPTIME_UPDATE = 5;
81
- /**
82
- * Websocket message ID indicating a snackbar message.
83
- *
84
- * @constant {number}
85
- */
86
23
  export const WS_ID_SNACKBAR = 6;
87
- /**
88
- * Websocket message ID indicating matterbridge has un update available.
89
- *
90
- * @constant {number}
91
- */
92
24
  export const WS_ID_UPDATE_NEEDED = 7;
93
- /**
94
- * Websocket message ID indicating a state update.
95
- *
96
- * @constant {number}
97
- */
98
25
  export const WS_ID_STATEUPDATE = 8;
99
- /**
100
- * Websocket message ID indicating to close a permanent snackbar message.
101
- *
102
- * @constant {number}
103
- */
104
26
  export const WS_ID_CLOSE_SNACKBAR = 9;
105
- /**
106
- * Websocket message ID indicating a shelly system update.
107
- * check:
108
- * curl -k http://127.0.0.1:8101/api/updates/sys/check
109
- * perform:
110
- * curl -k http://127.0.0.1:8101/api/updates/sys/perform
111
- *
112
- * @constant {number}
113
- */
114
27
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
115
- /**
116
- * Websocket message ID indicating a shelly main update.
117
- * check:
118
- * curl -k http://127.0.0.1:8101/api/updates/main/check
119
- * perform:
120
- * curl -k http://127.0.0.1:8101/api/updates/main/perform
121
- *
122
- * @constant {number}
123
- */
124
28
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
125
29
  export class Frontend extends EventEmitter {
126
30
  matterbridge;
@@ -134,7 +38,7 @@ export class Frontend extends EventEmitter {
134
38
  constructor(matterbridge) {
135
39
  super();
136
40
  this.matterbridge = matterbridge;
137
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
41
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
138
42
  }
139
43
  set logLevel(logLevel) {
140
44
  this.log.logLevel = logLevel;
@@ -142,41 +46,12 @@ export class Frontend extends EventEmitter {
142
46
  async start(port = 8283) {
143
47
  this.port = port;
144
48
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
145
- // Initialize multer with the upload directory
146
49
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
147
50
  await fs.mkdir(uploadDir, { recursive: true });
148
51
  const upload = multer({ dest: uploadDir });
149
- // Create the express app that serves the frontend
150
52
  this.expressApp = express();
151
- // Inject logging/debug wrapper for route/middleware registration
152
- /*
153
- const methods = ['get', 'post', 'put', 'delete', 'use'];
154
- for (const method of methods) {
155
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
- const original = (this.expressApp as any)[method].bind(this.expressApp);
157
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
159
- try {
160
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
161
- return original(path, ...rest);
162
- } catch (err) {
163
- console.error(`[ERROR] Failed to register route: ${path}`);
164
- throw err;
165
- }
166
- };
167
- }
168
- */
169
- // Log all requests to the server for debugging
170
- /*
171
- this.expressApp.use((req, res, next) => {
172
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
173
- next();
174
- });
175
- */
176
- // Serve static files from '/static' endpoint
177
53
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
178
54
  if (!hasParameter('ssl')) {
179
- // Create an HTTP server and attach the express app
180
55
  try {
181
56
  this.httpServer = createServer(this.expressApp);
182
57
  }
@@ -185,7 +60,6 @@ export class Frontend extends EventEmitter {
185
60
  this.emit('server_error', error);
186
61
  return;
187
62
  }
188
- // Listen on the specified port
189
63
  if (hasParameter('ingress')) {
190
64
  this.httpServer.listen(this.port, '0.0.0.0', () => {
191
65
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -217,7 +91,6 @@ export class Frontend extends EventEmitter {
217
91
  });
218
92
  }
219
93
  else {
220
- // Load the SSL certificate, the private key and optionally the CA certificate
221
94
  let cert;
222
95
  try {
223
96
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -247,7 +120,6 @@ export class Frontend extends EventEmitter {
247
120
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
248
121
  }
249
122
  const serverOptions = { cert, key, ca };
250
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
251
123
  try {
252
124
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
253
125
  }
@@ -256,7 +128,6 @@ export class Frontend extends EventEmitter {
256
128
  this.emit('server_error', error);
257
129
  return;
258
130
  }
259
- // Listen on the specified port
260
131
  if (hasParameter('ingress')) {
261
132
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
262
133
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -289,18 +160,16 @@ export class Frontend extends EventEmitter {
289
160
  }
290
161
  if (this.initializeError)
291
162
  return;
292
- // Create a WebSocket server and attach it to the http or https server
293
163
  const wssPort = this.port;
294
164
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
295
165
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
296
166
  this.webSocketServer.on('connection', (ws, request) => {
297
167
  const clientIp = request.socket.remoteAddress;
298
- // Set the global logger callback for the WebSocketServer
299
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
300
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
301
- callbackLogLevel = "info" /* LogLevel.INFO */;
302
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
303
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
168
+ let callbackLogLevel = "notice";
169
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
170
+ callbackLogLevel = "info";
171
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
172
+ callbackLogLevel = "debug";
304
173
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
305
174
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
306
175
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -335,7 +204,6 @@ export class Frontend extends EventEmitter {
335
204
  this.webSocketServer.on('error', (ws, error) => {
336
205
  this.log.error(`WebSocketServer error: ${error}`);
337
206
  });
338
- // Subscribe to cli events
339
207
  cliEmitter.removeAllListeners();
340
208
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
341
209
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -346,8 +214,6 @@ export class Frontend extends EventEmitter {
346
214
  cliEmitter.on('cpu', (cpuUsage) => {
347
215
  this.wssSendCpuUpdate(cpuUsage);
348
216
  });
349
- // Endpoint to validate login code
350
- // curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
351
217
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
352
218
  const { password } = req.body;
353
219
  this.log.debug('The frontend sent /api/login', password);
@@ -366,27 +232,23 @@ export class Frontend extends EventEmitter {
366
232
  this.log.warn('/api/login error wrong password');
367
233
  res.json({ valid: false });
368
234
  }
369
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
370
235
  }
371
236
  catch (error) {
372
237
  this.log.error('/api/login error getting password');
373
238
  res.json({ valid: false });
374
239
  }
375
240
  });
376
- // Endpoint to provide health check for docker
377
241
  this.expressApp.get('/health', (req, res) => {
378
242
  this.log.debug('Express received /health');
379
243
  const healthStatus = {
380
- status: 'ok', // Indicate service is healthy
381
- uptime: process.uptime(), // Server uptime in seconds
382
- timestamp: new Date().toISOString(), // Current timestamp
244
+ status: 'ok',
245
+ uptime: process.uptime(),
246
+ timestamp: new Date().toISOString(),
383
247
  };
384
248
  res.status(200).json(healthStatus);
385
249
  });
386
- // Endpoint to provide memory usage details
387
250
  this.expressApp.get('/memory', async (req, res) => {
388
251
  this.log.debug('Express received /memory');
389
- // Memory usage from process
390
252
  const memoryUsageRaw = process.memoryUsage();
391
253
  const memoryUsage = {
392
254
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -395,13 +257,10 @@ export class Frontend extends EventEmitter {
395
257
  external: this.formatMemoryUsage(memoryUsageRaw.external),
396
258
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
397
259
  };
398
- // V8 heap statistics
399
260
  const { default: v8 } = await import('node:v8');
400
261
  const heapStatsRaw = v8.getHeapStatistics();
401
262
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
402
- // Format heapStats
403
263
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
404
- // Format heapSpaces
405
264
  const heapSpaces = heapSpacesRaw.map((space) => ({
406
265
  ...space,
407
266
  space_size: this.formatMemoryUsage(space.space_size),
@@ -419,36 +278,31 @@ export class Frontend extends EventEmitter {
419
278
  };
420
279
  res.status(200).json(memoryReport);
421
280
  });
422
- // Endpoint to provide settings
423
281
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
424
282
  this.log.debug('The frontend sent /api/settings');
425
283
  res.json(await this.getApiSettings());
426
284
  });
427
- // Endpoint to provide plugins
428
285
  this.expressApp.get('/api/plugins', async (req, res) => {
429
286
  this.log.debug('The frontend sent /api/plugins');
430
287
  res.json(this.getBaseRegisteredPlugins());
431
288
  });
432
- // Endpoint to provide devices
433
289
  this.expressApp.get('/api/devices', async (req, res) => {
434
290
  this.log.debug('The frontend sent /api/devices');
435
291
  const devices = await this.getDevices();
436
292
  res.json(devices);
437
293
  });
438
- // Endpoint to view the matterbridge log
439
294
  this.expressApp.get('/api/view-mblog', async (req, res) => {
440
295
  this.log.debug('The frontend sent /api/view-mblog');
441
296
  try {
442
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
297
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
443
298
  res.type('text/plain');
444
299
  res.send(data);
445
300
  }
446
301
  catch (error) {
447
- this.log.error(`Error reading matterbridge log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
302
+ this.log.error(`Error reading matterbridge log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
448
303
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
449
304
  }
450
305
  });
451
- // Endpoint to view the matter.js log
452
306
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
453
307
  this.log.debug('The frontend sent /api/view-mjlog');
454
308
  try {
@@ -461,7 +315,6 @@ export class Frontend extends EventEmitter {
461
315
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
462
316
  }
463
317
  });
464
- // Endpoint to view the shelly log
465
318
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
466
319
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
467
320
  try {
@@ -470,38 +323,32 @@ export class Frontend extends EventEmitter {
470
323
  res.send(data);
471
324
  }
472
325
  catch (error) {
473
- this.log.error(`Error reading shelly log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
326
+ this.log.error(`Error reading shelly log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
474
327
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
475
328
  }
476
329
  });
477
- // Endpoint to download the matterbridge log
478
330
  this.expressApp.get('/api/download-mblog', async (req, res) => {
479
- this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
331
+ this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
480
332
  try {
481
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
482
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
483
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
484
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
333
+ await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
334
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
335
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
485
336
  }
486
337
  catch (error) {
487
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
338
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
488
339
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
489
340
  }
490
341
  res.type('text/plain');
491
- // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
492
- res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
493
- /* istanbul ignore if */
342
+ res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
494
343
  if (error) {
495
- this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
344
+ this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
496
345
  res.status(500).send('Error downloading the matterbridge log file');
497
346
  }
498
347
  });
499
348
  });
500
- // Endpoint to download the matter log
501
349
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
502
- this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
350
+ this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
503
351
  try {
504
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
505
352
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
506
353
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
507
354
  await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
@@ -512,18 +359,15 @@ export class Frontend extends EventEmitter {
512
359
  }
513
360
  res.type('text/plain');
514
361
  res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
515
- /* istanbul ignore if */
516
362
  if (error) {
517
363
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
518
364
  res.status(500).send('Error downloading the matter log file');
519
365
  }
520
366
  });
521
367
  });
522
- // Endpoint to download the shelly log
523
368
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
524
369
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
525
370
  try {
526
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
527
371
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
528
372
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
529
373
  await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
@@ -534,14 +378,12 @@ export class Frontend extends EventEmitter {
534
378
  }
535
379
  res.type('text/plain');
536
380
  res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
537
- /* istanbul ignore if */
538
381
  if (error) {
539
382
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
540
383
  res.status(500).send('Error downloading Shelly system log file');
541
384
  }
542
385
  });
543
386
  });
544
- // Endpoint to download the matterbridge storage directory
545
387
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
546
388
  this.log.debug('The frontend sent /api/download-mbstorage');
547
389
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -552,7 +394,6 @@ export class Frontend extends EventEmitter {
552
394
  }
553
395
  });
554
396
  });
555
- // Endpoint to download the matter storage file
556
397
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
557
398
  this.log.debug('The frontend sent /api/download-mjstorage');
558
399
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -563,7 +404,6 @@ export class Frontend extends EventEmitter {
563
404
  }
564
405
  });
565
406
  });
566
- // Endpoint to download the matterbridge plugin directory
567
407
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
568
408
  this.log.debug('The frontend sent /api/download-pluginstorage');
569
409
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -574,18 +414,16 @@ export class Frontend extends EventEmitter {
574
414
  }
575
415
  });
576
416
  });
577
- // Endpoint to download the matterbridge plugin config files
578
417
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
579
418
  this.log.debug('The frontend sent /api/download-pluginconfig');
580
419
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
581
420
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
582
421
  if (error) {
583
- this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
584
- res.status(500).send('Error downloading the matterbridge plugin storage file');
422
+ this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
423
+ res.status(500).send('Error downloading the matterbridge plugin config file');
585
424
  }
586
425
  });
587
426
  });
588
- // Endpoint to download the matterbridge backup (created with the backup command)
589
427
  this.expressApp.get('/api/download-backup', async (req, res) => {
590
428
  this.log.debug('The frontend sent /api/download-backup');
591
429
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -595,7 +433,6 @@ export class Frontend extends EventEmitter {
595
433
  }
596
434
  });
597
435
  });
598
- // Endpoint to upload a package
599
436
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
600
437
  const { filename } = req.body;
601
438
  const file = req.file;
@@ -605,13 +442,10 @@ export class Frontend extends EventEmitter {
605
442
  return;
606
443
  }
607
444
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
608
- // Define the path where the plugin file will be saved
609
445
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
610
446
  try {
611
- // Move the uploaded file to the specified path
612
447
  await fs.rename(file.path, filePath);
613
448
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
614
- // Install the plugin package
615
449
  if (filename.endsWith('.tgz')) {
616
450
  const { spawnCommand } = await import('./utils/spawn.js');
617
451
  await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
@@ -631,7 +465,6 @@ export class Frontend extends EventEmitter {
631
465
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
632
466
  }
633
467
  });
634
- // Fallback for routing (must be the last route)
635
468
  this.expressApp.use((req, res) => {
636
469
  this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
637
470
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -640,15 +473,13 @@ export class Frontend extends EventEmitter {
640
473
  }
641
474
  async stop() {
642
475
  this.log.debug('Stopping the frontend...');
643
- // Remove listeners from the express app
644
476
  if (this.expressApp) {
645
477
  this.expressApp.removeAllListeners();
646
478
  this.expressApp = undefined;
647
479
  this.log.debug('Frontend app closed successfully');
648
480
  }
649
- // Close the WebSocket server
650
481
  if (this.webSocketServer) {
651
- // Close all active connections
482
+ this.log.debug('Closing WebSocket server...');
652
483
  this.webSocketServer.clients.forEach((client) => {
653
484
  if (client.readyState === WebSocket.OPEN) {
654
485
  client.close();
@@ -668,8 +499,8 @@ export class Frontend extends EventEmitter {
668
499
  this.webSocketServer.removeAllListeners();
669
500
  this.webSocketServer = undefined;
670
501
  }
671
- // Close the http server
672
502
  if (this.httpServer) {
503
+ this.log.debug('Closing http server...');
673
504
  await withTimeout(new Promise((resolve) => {
674
505
  this.httpServer?.close((error) => {
675
506
  if (error) {
@@ -685,8 +516,8 @@ export class Frontend extends EventEmitter {
685
516
  this.httpServer = undefined;
686
517
  this.log.debug('Frontend http server closed successfully');
687
518
  }
688
- // Close the https server
689
519
  if (this.httpsServer) {
520
+ this.log.debug('Closing https server...');
690
521
  await withTimeout(new Promise((resolve) => {
691
522
  this.httpsServer?.close((error) => {
692
523
  if (error) {
@@ -704,7 +535,6 @@ export class Frontend extends EventEmitter {
704
535
  }
705
536
  this.log.debug('Frontend stopped successfully');
706
537
  }
707
- // Function to format bytes to KB, MB, or GB
708
538
  formatMemoryUsage = (bytes) => {
709
539
  if (bytes >= 1024 ** 3) {
710
540
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -716,7 +546,6 @@ export class Frontend extends EventEmitter {
716
546
  return `${(bytes / 1024).toFixed(2)} KB`;
717
547
  }
718
548
  };
719
- // Function to format system uptime with only the most significant unit
720
549
  formatOsUpTime = (seconds) => {
721
550
  if (seconds >= 86400) {
722
551
  const days = Math.floor(seconds / 86400);
@@ -732,13 +561,7 @@ export class Frontend extends EventEmitter {
732
561
  }
733
562
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
734
563
  };
735
- /**
736
- * Retrieves the api settings data.
737
- *
738
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
739
- */
740
564
  async getApiSettings() {
741
- // Update the system information
742
565
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
743
566
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
744
567
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -747,7 +570,6 @@ export class Frontend extends EventEmitter {
747
570
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
748
571
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
749
572
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
750
- // Update the matterbridge information
751
573
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
752
574
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
753
575
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -766,12 +588,6 @@ export class Frontend extends EventEmitter {
766
588
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
767
589
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
768
590
  }
769
- /**
770
- * Retrieves the reachable attribute.
771
- *
772
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
773
- * @returns {boolean} The reachable attribute.
774
- */
775
591
  getReachability(device) {
776
592
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
777
593
  return false;
@@ -783,12 +599,6 @@ export class Frontend extends EventEmitter {
783
599
  return true;
784
600
  return false;
785
601
  }
786
- /**
787
- * Retrieves the power source attribute.
788
- *
789
- * @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
790
- * @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
791
- */
792
602
  getPowerSource(endpoint) {
793
603
  if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
794
604
  return undefined;
@@ -804,21 +614,13 @@ export class Frontend extends EventEmitter {
804
614
  }
805
615
  return;
806
616
  };
807
- // Root endpoint
808
617
  if (endpoint.hasClusterServer(PowerSource.Cluster.id))
809
618
  return powerSource(endpoint);
810
- // Child endpoints
811
619
  for (const child of endpoint.getChildEndpoints()) {
812
620
  if (child.hasClusterServer(PowerSource.Cluster.id))
813
621
  return powerSource(child);
814
622
  }
815
623
  }
816
- /**
817
- * Retrieves the matter pairing code from a given device.
818
- *
819
- * @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object to retrieve the QR pairing code from.
820
- * @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
821
- */
822
624
  getMatterDataFromDevice(device) {
823
625
  if (device.mode === 'server' && device.serverNode && device.serverContext) {
824
626
  return {
@@ -830,65 +632,30 @@ export class Frontend extends EventEmitter {
830
632
  };
831
633
  }
832
634
  }
833
- /**
834
- * Retrieves the cluster text description from a given device.
835
- *
836
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
837
- * @returns {string} The attributes description of the cluster servers in the device.
838
- */
839
635
  getClusterTextFromDevice(device) {
840
636
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
841
637
  return '';
842
- const getAttribute = (device, cluster, attribute) => {
843
- let value = undefined;
844
- Object.entries(device.state)
845
- .filter(([clusterName]) => clusterName.toLowerCase() === cluster.toLowerCase())
846
- .forEach(([, clusterAttributes]) => {
847
- Object.entries(clusterAttributes)
848
- .filter(([attributeName]) => attributeName.toLowerCase() === attribute.toLowerCase())
849
- .forEach(([, attributeValue]) => {
850
- value = attributeValue;
851
- });
852
- });
853
- if (value === undefined)
854
- this.log.error(`Cluster ${cluster} or attribute ${attribute} not found in device ${device.deviceName}`);
855
- return value;
856
- };
857
638
  const getUserLabel = (device) => {
858
639
  const labelList = getAttribute(device, 'userLabel', 'labelList');
859
- if (!labelList)
860
- return;
861
- const composed = labelList.find((entry) => entry.label === 'composed');
862
- if (composed)
863
- return 'Composed: ' + composed.value;
864
- else
865
- return '';
640
+ if (labelList) {
641
+ const composed = labelList.find((entry) => entry.label === 'composed');
642
+ if (composed)
643
+ return 'Composed: ' + composed.value;
644
+ }
645
+ return '';
866
646
  };
867
647
  const getFixedLabel = (device) => {
868
648
  const labelList = getAttribute(device, 'fixedLabel', 'labelList');
869
- if (!labelList)
870
- return;
871
- const composed = labelList.find((entry) => entry.label === 'composed');
872
- if (composed)
873
- return 'Composed: ' + composed.value;
874
- else
875
- return '';
649
+ if (labelList) {
650
+ const composed = labelList.find((entry) => entry.label === 'composed');
651
+ if (composed)
652
+ return 'Composed: ' + composed.value;
653
+ }
654
+ return '';
876
655
  };
877
656
  let attributes = '';
878
657
  let supportedModes = [];
879
- /*
880
- Object.keys(device.behaviors.supported).forEach((clusterName) => {
881
- const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
882
- // console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
883
- if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
884
- Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
885
- // console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
886
- });
887
- }
888
- });
889
- */
890
658
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
891
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
892
659
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
893
660
  return;
894
661
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -913,8 +680,6 @@ export class Frontend extends EventEmitter {
913
680
  const supportedMode = supportedModes.find((mode) => mode.mode === attributeValue);
914
681
  if (supportedMode)
915
682
  attributes += `Mode: ${supportedMode.label} `;
916
- else
917
- attributes += `Mode: ${attributeValue} `;
918
683
  }
919
684
  const operationalStateClusters = ['operationalState', 'rvcOperationalState'];
920
685
  if (operationalStateClusters.includes(clusterName) && attributeName === 'operationalState')
@@ -980,14 +745,8 @@ export class Frontend extends EventEmitter {
980
745
  if (clusterName === 'userLabel' && attributeName === 'labelList')
981
746
  attributes += `${getUserLabel(device)} `;
982
747
  });
983
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
984
748
  return attributes.trimStart().trimEnd();
985
749
  }
986
- /**
987
- * Retrieves the base registered plugins sanitized for res.json().
988
- *
989
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
990
- */
991
750
  getBaseRegisteredPlugins() {
992
751
  const baseRegisteredPlugins = [];
993
752
  for (const plugin of this.matterbridge.plugins) {
@@ -1026,19 +785,11 @@ export class Frontend extends EventEmitter {
1026
785
  }
1027
786
  return baseRegisteredPlugins;
1028
787
  }
1029
- /**
1030
- * Retrieves the devices from Matterbridge.
1031
- *
1032
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
1033
- * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
1034
- */
1035
788
  async getDevices(pluginName) {
1036
789
  const devices = [];
1037
790
  for (const device of this.matterbridge.devices.array()) {
1038
- // Filter by pluginName if provided
1039
791
  if (pluginName && pluginName !== device.plugin)
1040
792
  continue;
1041
- // Check if the device has the required properties
1042
793
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1043
794
  continue;
1044
795
  devices.push({
@@ -1058,37 +809,22 @@ export class Frontend extends EventEmitter {
1058
809
  }
1059
810
  return devices;
1060
811
  }
1061
- /**
1062
- * Retrieves the clusters from a given plugin and endpoint number.
1063
- *
1064
- * Response for /api/clusters
1065
- *
1066
- * @param {string} pluginName - The name of the plugin.
1067
- * @param {number} endpointNumber - The endpoint number.
1068
- * @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
1069
- */
1070
812
  getClusters(pluginName, endpointNumber) {
1071
813
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1072
814
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
1073
815
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1074
816
  return;
1075
817
  }
1076
- // this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
1077
- // Get the device types from the main endpoint
1078
818
  const deviceTypes = [];
1079
819
  const clusters = [];
1080
820
  endpoint.state.descriptor.deviceTypeList.forEach((d) => {
1081
821
  deviceTypes.push(d.deviceType);
1082
822
  });
1083
- // Get the clusters from the main endpoint
1084
823
  endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1085
824
  if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1086
825
  return;
1087
826
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1088
827
  return;
1089
- // console.log(
1090
- // `${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}`,
1091
- // );
1092
828
  clusters.push({
1093
829
  endpoint: endpoint.number.toString(),
1094
830
  id: 'main',
@@ -1101,18 +837,12 @@ export class Frontend extends EventEmitter {
1101
837
  attributeLocalValue: attributeValue,
1102
838
  });
1103
839
  });
1104
- // Get the child endpoints
1105
840
  const childEndpoints = endpoint.getChildEndpoints();
1106
- // if (childEndpoints.length === 0) {
1107
- // this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
1108
- // }
1109
841
  childEndpoints.forEach((childEndpoint) => {
1110
842
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1111
843
  this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1112
844
  return;
1113
845
  }
1114
- // this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
1115
- // Get the device types of the child endpoint
1116
846
  const deviceTypes = [];
1117
847
  childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
1118
848
  deviceTypes.push(d.deviceType);
@@ -1122,12 +852,9 @@ export class Frontend extends EventEmitter {
1122
852
  return;
1123
853
  if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
1124
854
  return;
1125
- // console.log(
1126
- // `${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}`,
1127
- // );
1128
855
  clusters.push({
1129
856
  endpoint: childEndpoint.number.toString(),
1130
- id: childEndpoint.maybeId ?? 'null', // Never happens
857
+ id: childEndpoint.maybeId ?? 'null',
1131
858
  deviceTypes,
1132
859
  clusterName: capitalizeFirstLetter(clusterName),
1133
860
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -1140,13 +867,6 @@ export class Frontend extends EventEmitter {
1140
867
  });
1141
868
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
1142
869
  }
1143
- /**
1144
- * Handles incoming websocket messages for the Matterbridge frontend.
1145
- *
1146
- * @param {WebSocket} client - The websocket client that sent the message.
1147
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1148
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1149
- */
1150
870
  async wsMessageHandler(client, message) {
1151
871
  let data;
1152
872
  try {
@@ -1193,41 +913,32 @@ export class Frontend extends EventEmitter {
1193
913
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
1194
914
  const packageName = data.params.packageName.replace(/@.*$/, '');
1195
915
  if (data.params.restart === false && packageName !== 'matterbridge') {
1196
- // The install comes from InstallPlugins
1197
916
  this.matterbridge.plugins
1198
917
  .add(packageName)
1199
918
  .then((plugin) => {
1200
919
  if (plugin) {
1201
- // The plugin is not registered
1202
920
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1203
921
  this.matterbridge.plugins
1204
922
  .load(plugin, true, 'The plugin has been added', true)
1205
- // eslint-disable-next-line promise/no-nesting
1206
923
  .then(() => {
1207
924
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
1208
925
  this.wssSendRefreshRequired('plugins');
1209
926
  return;
1210
927
  })
1211
- // eslint-disable-next-line promise/no-nesting
1212
928
  .catch((_error) => {
1213
- //
1214
929
  });
1215
930
  }
1216
931
  else {
1217
- // The plugin is already registered
1218
932
  this.wssSendSnackbarMessage(`Restart required`, 0);
1219
933
  this.wssSendRefreshRequired('plugins');
1220
934
  this.wssSendRestartRequired();
1221
935
  }
1222
936
  return;
1223
937
  })
1224
- // eslint-disable-next-line promise/no-nesting
1225
938
  .catch((_error) => {
1226
- //
1227
939
  });
1228
940
  }
1229
941
  else {
1230
- // The package is matterbridge
1231
942
  if (this.matterbridge.restartMode !== '') {
1232
943
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1233
944
  this.matterbridge.shutdownProcess();
@@ -1250,7 +961,6 @@ export class Frontend extends EventEmitter {
1250
961
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1251
962
  return;
1252
963
  }
1253
- // The package is a plugin
1254
964
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1255
965
  if (plugin) {
1256
966
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -1259,7 +969,6 @@ export class Frontend extends EventEmitter {
1259
969
  this.wssSendRefreshRequired('plugins');
1260
970
  this.wssSendRefreshRequired('devices');
1261
971
  }
1262
- // Uninstall the package
1263
972
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1264
973
  const { spawnCommand } = await import('./utils/spawn.js');
1265
974
  spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1300,7 +1009,6 @@ export class Frontend extends EventEmitter {
1300
1009
  return;
1301
1010
  })
1302
1011
  .catch((_error) => {
1303
- //
1304
1012
  });
1305
1013
  }
1306
1014
  else {
@@ -1347,7 +1055,6 @@ export class Frontend extends EventEmitter {
1347
1055
  return;
1348
1056
  })
1349
1057
  .catch((_error) => {
1350
- //
1351
1058
  });
1352
1059
  }
1353
1060
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1376,10 +1083,7 @@ export class Frontend extends EventEmitter {
1376
1083
  }
1377
1084
  this.log.info(`Saving config for plugin ${plg}${data.params.pluginName}${nf}...`);
1378
1085
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1379
- if (!plugin) {
1380
- this.log.warn(`Plugin ${plg}${data.params.pluginName}${wr} not found in matterbridge`);
1381
- }
1382
- else {
1086
+ if (plugin) {
1383
1087
  this.matterbridge.plugins.saveConfigFromJson(plugin, data.params.formData, true);
1384
1088
  this.wssSendSnackbarMessage(`Saved config for plugin ${data.params.pluginName}`);
1385
1089
  this.wssSendRefreshRequired('pluginsRestart');
@@ -1465,7 +1169,7 @@ export class Frontend extends EventEmitter {
1465
1169
  this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
1466
1170
  this.wssSendRefreshRequired('matterbridgeAdvertise');
1467
1171
  this.wssSendSnackbarMessage(`Started fabrics share`, 0);
1468
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes }));
1172
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
1469
1173
  }
1470
1174
  else if (data.method === '/api/stopadvertise') {
1471
1175
  await this.matterbridge.stopAdvertiseServerNode(this.matterbridge.serverNode);
@@ -1585,22 +1289,22 @@ export class Frontend extends EventEmitter {
1585
1289
  if (isValidString(data.params.value, 4)) {
1586
1290
  this.log.debug('Matterbridge logger level:', data.params.value);
1587
1291
  if (data.params.value === 'Debug') {
1588
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1292
+ await this.matterbridge.setLogLevel("debug");
1589
1293
  }
1590
1294
  else if (data.params.value === 'Info') {
1591
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1295
+ await this.matterbridge.setLogLevel("info");
1592
1296
  }
1593
1297
  else if (data.params.value === 'Notice') {
1594
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1298
+ await this.matterbridge.setLogLevel("notice");
1595
1299
  }
1596
1300
  else if (data.params.value === 'Warn') {
1597
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1301
+ await this.matterbridge.setLogLevel("warn");
1598
1302
  }
1599
1303
  else if (data.params.value === 'Error') {
1600
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1304
+ await this.matterbridge.setLogLevel("error");
1601
1305
  }
1602
1306
  else if (data.params.value === 'Fatal') {
1603
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1307
+ await this.matterbridge.setLogLevel("fatal");
1604
1308
  }
1605
1309
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1606
1310
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1611,9 +1315,8 @@ export class Frontend extends EventEmitter {
1611
1315
  this.log.debug('Matterbridge file log:', data.params.value);
1612
1316
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1613
1317
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1614
- // Create the file logger for matterbridge
1615
1318
  if (data.params.value)
1616
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1319
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1617
1320
  else
1618
1321
  AnsiLogger.setGlobalLogfile(undefined);
1619
1322
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1725,14 +1428,15 @@ export class Frontend extends EventEmitter {
1725
1428
  this.matterbridge.matterbridgeInformation.matterDiscriminator = data.params.value;
1726
1429
  await this.matterbridge.nodeContext?.set('matterdiscriminator', data.params.value);
1727
1430
  this.wssSendRestartRequired();
1431
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1728
1432
  }
1729
1433
  else {
1730
1434
  this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
1731
1435
  this.matterbridge.matterbridgeInformation.matterDiscriminator = undefined;
1732
1436
  await this.matterbridge.nodeContext?.remove('matterdiscriminator');
1733
1437
  this.wssSendRestartRequired();
1438
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: false }));
1734
1439
  }
1735
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1736
1440
  break;
1737
1441
  case 'setmatterpasscode':
1738
1442
  data.params.value = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
@@ -1741,14 +1445,15 @@ export class Frontend extends EventEmitter {
1741
1445
  this.log.debug(`Set matter commissioning passcode to ${CYAN}${data.params.value}${db}`);
1742
1446
  await this.matterbridge.nodeContext?.set('matterpasscode', data.params.value);
1743
1447
  this.wssSendRestartRequired();
1448
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1744
1449
  }
1745
1450
  else {
1746
1451
  this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
1747
1452
  this.matterbridge.matterbridgeInformation.matterPasscode = undefined;
1748
1453
  await this.matterbridge.nodeContext?.remove('matterpasscode');
1749
1454
  this.wssSendRestartRequired();
1455
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: false }));
1750
1456
  }
1751
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1752
1457
  break;
1753
1458
  case 'setvirtualmode':
1754
1459
  if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
@@ -1776,19 +1481,15 @@ export class Frontend extends EventEmitter {
1776
1481
  return;
1777
1482
  }
1778
1483
  const config = plugin.configJson;
1779
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1780
1484
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1781
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1782
1485
  if (select === 'serial')
1783
1486
  this.log.info(`Selected device serial ${data.params.serial}`);
1784
1487
  if (select === 'name')
1785
1488
  this.log.info(`Selected device name ${data.params.name}`);
1786
1489
  if (config && select && (select === 'serial' || select === 'name')) {
1787
- // Remove postfix from the serial if it exists
1788
1490
  if (config.postfix) {
1789
1491
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1790
1492
  }
1791
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1792
1493
  if (isValidArray(config.whiteList, 1)) {
1793
1494
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1794
1495
  config.whiteList.push(data.params.serial);
@@ -1797,7 +1498,6 @@ export class Frontend extends EventEmitter {
1797
1498
  config.whiteList.push(data.params.name);
1798
1499
  }
1799
1500
  }
1800
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1801
1501
  if (isValidArray(config.blackList, 1)) {
1802
1502
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1803
1503
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1827,9 +1527,7 @@ export class Frontend extends EventEmitter {
1827
1527
  return;
1828
1528
  }
1829
1529
  const config = plugin.configJson;
1830
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1831
1530
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1832
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1833
1531
  if (select === 'serial')
1834
1532
  this.log.info(`Unselected device serial ${data.params.serial}`);
1835
1533
  if (select === 'name')
@@ -1838,7 +1536,6 @@ export class Frontend extends EventEmitter {
1838
1536
  if (config.postfix) {
1839
1537
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1840
1538
  }
1841
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1842
1539
  if (isValidArray(config.whiteList, 1)) {
1843
1540
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1844
1541
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1847,7 +1544,6 @@ export class Frontend extends EventEmitter {
1847
1544
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1848
1545
  }
1849
1546
  }
1850
- // Add the serial to the blackList
1851
1547
  if (isValidArray(config.blackList)) {
1852
1548
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1853
1549
  config.blackList.push(data.params.serial);
@@ -1880,230 +1576,114 @@ export class Frontend extends EventEmitter {
1880
1576
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1881
1577
  }
1882
1578
  }
1883
- /**
1884
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1885
- *
1886
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1887
- * @param {string} time - The time string of the message
1888
- * @param {string} name - The logger name of the message
1889
- * @param {string} message - The content of the message.
1890
- *
1891
- * @remarks
1892
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1893
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1894
- * The function sends the message to all connected clients.
1895
- */
1896
1579
  wssSendMessage(level, time, name, message) {
1897
1580
  if (!level || !time || !name || !message)
1898
1581
  return;
1899
- // Remove ANSI escape codes from the message
1900
- // eslint-disable-next-line no-control-regex
1901
1582
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1902
- // Remove leading asterisks from the message
1903
1583
  message = message.replace(/^\*+/, '');
1904
- // Replace all occurrences of \t and \n
1905
1584
  message = message.replace(/[\t\n]/g, '');
1906
- // Remove non-printable characters
1907
- // eslint-disable-next-line no-control-regex
1908
1585
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1909
- // Replace all occurrences of \" with "
1910
1586
  message = message.replace(/\\"/g, '"');
1911
- // Replace all occurrences of angle-brackets with &lt; and &gt;"
1912
1587
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1913
- // Define the maximum allowed length for continuous characters without a space
1914
1588
  const maxContinuousLength = 100;
1915
1589
  const keepStartLength = 20;
1916
1590
  const keepEndLength = 20;
1917
- // Split the message into words
1918
1591
  message = message
1919
1592
  .split(' ')
1920
1593
  .map((word) => {
1921
- // If the word length exceeds the max continuous length, insert spaces and truncate
1922
1594
  if (word.length > maxContinuousLength) {
1923
1595
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1924
1596
  }
1925
1597
  return word;
1926
1598
  })
1927
1599
  .join(' ');
1928
- // Send the message to all connected clients
1929
1600
  this.webSocketServer?.clients.forEach((client) => {
1930
1601
  if (client.readyState === WebSocket.OPEN) {
1931
1602
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1932
1603
  }
1933
1604
  });
1934
1605
  }
1935
- /**
1936
- * Sends a need to refresh WebSocket message to all connected clients.
1937
- *
1938
- * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1939
- * possible values:
1940
- * - 'matterbridgeLatestVersion'
1941
- * - 'matterbridgeAdvertise'
1942
- * - 'online'
1943
- * - 'offline'
1944
- * - 'reachability'
1945
- * - 'settings'
1946
- * - 'plugins'
1947
- * - 'pluginsRestart'
1948
- * - 'devices'
1949
- * - 'fabrics'
1950
- * - 'sessions'
1951
- */
1952
1606
  wssSendRefreshRequired(changed = null) {
1953
1607
  this.log.debug('Sending a refresh required message to all connected clients');
1954
- // Send the message to all connected clients
1955
1608
  this.webSocketServer?.clients.forEach((client) => {
1956
1609
  if (client.readyState === WebSocket.OPEN) {
1957
1610
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1958
1611
  }
1959
1612
  });
1960
1613
  }
1961
- /**
1962
- * Sends a need to restart WebSocket message to all connected clients.
1963
- *
1964
- * @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
1965
- */
1966
1614
  wssSendRestartRequired(snackbar = true) {
1967
1615
  this.log.debug('Sending a restart required message to all connected clients');
1968
1616
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1969
1617
  if (snackbar === true)
1970
1618
  this.wssSendSnackbarMessage(`Restart required`, 0);
1971
- // Send the message to all connected clients
1972
1619
  this.webSocketServer?.clients.forEach((client) => {
1973
1620
  if (client.readyState === WebSocket.OPEN) {
1974
1621
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1975
1622
  }
1976
1623
  });
1977
1624
  }
1978
- /**
1979
- * Sends a need to update WebSocket message to all connected clients.
1980
- *
1981
- */
1982
1625
  wssSendUpdateRequired() {
1983
1626
  this.log.debug('Sending an update required message to all connected clients');
1984
1627
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1985
- // Send the message to all connected clients
1986
1628
  this.webSocketServer?.clients.forEach((client) => {
1987
1629
  if (client.readyState === WebSocket.OPEN) {
1988
1630
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1989
1631
  }
1990
1632
  });
1991
1633
  }
1992
- /**
1993
- * Sends a cpu update message to all connected clients.
1994
- *
1995
- * @param {number} cpuUsage - The CPU usage percentage to send.
1996
- */
1997
1634
  wssSendCpuUpdate(cpuUsage) {
1998
1635
  if (hasParameter('debug'))
1999
1636
  this.log.debug('Sending a cpu update message to all connected clients');
2000
- // Send the message to all connected clients
2001
1637
  this.webSocketServer?.clients.forEach((client) => {
2002
1638
  if (client.readyState === WebSocket.OPEN) {
2003
1639
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
2004
1640
  }
2005
1641
  });
2006
1642
  }
2007
- /**
2008
- * Sends a memory update message to all connected clients.
2009
- *
2010
- * @param {string} totalMemory - The total memory in bytes.
2011
- * @param {string} freeMemory - The free memory in bytes.
2012
- * @param {string} rss - The resident set size in bytes.
2013
- * @param {string} heapTotal - The total heap memory in bytes.
2014
- * @param {string} heapUsed - The used heap memory in bytes.
2015
- * @param {string} external - The external memory in bytes.
2016
- * @param {string} arrayBuffers - The array buffers memory in bytes.
2017
- */
2018
1643
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
2019
1644
  if (hasParameter('debug'))
2020
1645
  this.log.debug('Sending a memory update message to all connected clients');
2021
- // Send the message to all connected clients
2022
1646
  this.webSocketServer?.clients.forEach((client) => {
2023
1647
  if (client.readyState === WebSocket.OPEN) {
2024
1648
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
2025
1649
  }
2026
1650
  });
2027
1651
  }
2028
- /**
2029
- * Sends an uptime update message to all connected clients.
2030
- *
2031
- * @param {string} systemUptime - The system uptime in a human-readable format.
2032
- * @param {string} processUptime - The process uptime in a human-readable format.
2033
- */
2034
1652
  wssSendUptimeUpdate(systemUptime, processUptime) {
2035
1653
  if (hasParameter('debug'))
2036
1654
  this.log.debug('Sending a uptime update message to all connected clients');
2037
- // Send the message to all connected clients
2038
1655
  this.webSocketServer?.clients.forEach((client) => {
2039
1656
  if (client.readyState === WebSocket.OPEN) {
2040
1657
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
2041
1658
  }
2042
1659
  });
2043
1660
  }
2044
- /**
2045
- * Sends an open snackbar message to all connected clients.
2046
- *
2047
- * @param {string} message - The message to send.
2048
- * @param {number} timeout - The timeout in seconds for the snackbar message.
2049
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
2050
- */
2051
1661
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
2052
1662
  this.log.debug('Sending a snackbar message to all connected clients');
2053
- // Send the message to all connected clients
2054
1663
  this.webSocketServer?.clients.forEach((client) => {
2055
1664
  if (client.readyState === WebSocket.OPEN) {
2056
1665
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
2057
1666
  }
2058
1667
  });
2059
1668
  }
2060
- /**
2061
- * Sends a close snackbar message to all connected clients.
2062
- *
2063
- * @param {string} message - The message to send.
2064
- */
2065
1669
  wssSendCloseSnackbarMessage(message) {
2066
1670
  this.log.debug('Sending a close snackbar message to all connected clients');
2067
- // Send the message to all connected clients
2068
1671
  this.webSocketServer?.clients.forEach((client) => {
2069
1672
  if (client.readyState === WebSocket.OPEN) {
2070
1673
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
2071
1674
  }
2072
1675
  });
2073
1676
  }
2074
- /**
2075
- * Sends an attribute update message to all connected WebSocket clients.
2076
- *
2077
- * @param {string | undefined} plugin - The name of the plugin.
2078
- * @param {string | undefined} serialNumber - The serial number of the device.
2079
- * @param {string | undefined} uniqueId - The unique identifier of the device.
2080
- * @param {string} cluster - The cluster name where the attribute belongs.
2081
- * @param {string} attribute - The name of the attribute that changed.
2082
- * @param {number | string | boolean} value - The new value of the attribute.
2083
- *
2084
- * @remarks
2085
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
2086
- * with the updated attribute information.
2087
- */
2088
1677
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
2089
1678
  this.log.debug('Sending an attribute update message to all connected clients');
2090
- // Send the message to all connected clients
2091
1679
  this.webSocketServer?.clients.forEach((client) => {
2092
1680
  if (client.readyState === WebSocket.OPEN) {
2093
1681
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
2094
1682
  }
2095
1683
  });
2096
1684
  }
2097
- /**
2098
- * Sends a message to all connected clients.
2099
- *
2100
- * @param {number} id - The message id.
2101
- * @param {string} method - The message method.
2102
- * @param {Record<string, string | number | boolean>} params - The message parameters.
2103
- */
2104
1685
  wssBroadcastMessage(id, method, params) {
2105
1686
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2106
- // Send the message to all connected clients
2107
1687
  this.webSocketServer?.clients.forEach((client) => {
2108
1688
  if (client.readyState === WebSocket.OPEN) {
2109
1689
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -2111,4 +1691,3 @@ export class Frontend extends EventEmitter {
2111
1691
  });
2112
1692
  }
2113
1693
  }
2114
- //# sourceMappingURL=frontend.js.map