matterbridge 3.0.2 → 3.0.3-dev-20250517-720018f

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 (164) hide show
  1. package/CHANGELOG.md +22 -3
  2. package/dist/cli.js +2 -37
  3. package/dist/cluster/export.js +0 -2
  4. package/dist/defaultConfigSchema.js +0 -23
  5. package/dist/deviceManager.js +1 -94
  6. package/dist/frontend.js +71 -369
  7. package/dist/helpers.js +25 -56
  8. package/dist/index.js +2 -31
  9. package/dist/logger/export.js +0 -1
  10. package/dist/matter/behaviors.js +0 -2
  11. package/dist/matter/clusters.js +0 -2
  12. package/dist/matter/devices.js +0 -2
  13. package/dist/matter/endpoints.js +0 -2
  14. package/dist/matter/export.js +0 -2
  15. package/dist/matter/types.js +0 -2
  16. package/dist/matterbridge.js +143 -873
  17. package/dist/matterbridgeAccessoryPlatform.js +0 -34
  18. package/dist/matterbridgeBehaviors.js +4 -53
  19. package/dist/matterbridgeDeviceTypes.js +34 -431
  20. package/dist/matterbridgeDynamicPlatform.js +0 -34
  21. package/dist/matterbridgeEndpoint.js +11 -807
  22. package/dist/matterbridgeEndpointHelpers.js +9 -147
  23. package/dist/matterbridgePlatform.js +7 -225
  24. package/dist/matterbridgeTypes.js +0 -24
  25. package/dist/pluginManager.js +3 -264
  26. package/dist/roboticVacuumCleaner.js +3 -39
  27. package/dist/shelly.js +7 -155
  28. package/dist/storage/export.js +0 -1
  29. package/dist/update.js +0 -53
  30. package/dist/utils/colorUtils.js +2 -205
  31. package/dist/utils/commandLine.js +0 -53
  32. package/dist/utils/copyDirectory.js +1 -37
  33. package/dist/utils/createZip.js +2 -42
  34. package/dist/utils/deepCopy.js +0 -38
  35. package/dist/utils/deepEqual.js +1 -71
  36. package/dist/utils/export.js +0 -1
  37. package/dist/utils/hex.js +0 -57
  38. package/dist/utils/isvalid.js +0 -100
  39. package/dist/utils/network.js +5 -76
  40. package/dist/utils/wait.js +20 -56
  41. package/frontend/build/asset-manifest.json +3 -3
  42. package/frontend/build/index.html +1 -1
  43. package/frontend/build/static/js/{main.f6e0f736.js → main.2486c3e3.js} +3 -3
  44. package/frontend/build/static/js/{main.f6e0f736.js.map → main.2486c3e3.js.map} +1 -1
  45. package/npm-shrinkwrap.json +2 -2
  46. package/package.json +1 -2
  47. package/dist/cli.d.ts +0 -29
  48. package/dist/cli.d.ts.map +0 -1
  49. package/dist/cli.js.map +0 -1
  50. package/dist/cluster/export.d.ts +0 -2
  51. package/dist/cluster/export.d.ts.map +0 -1
  52. package/dist/cluster/export.js.map +0 -1
  53. package/dist/defaultConfigSchema.d.ts +0 -27
  54. package/dist/defaultConfigSchema.d.ts.map +0 -1
  55. package/dist/defaultConfigSchema.js.map +0 -1
  56. package/dist/deviceManager.d.ts +0 -114
  57. package/dist/deviceManager.d.ts.map +0 -1
  58. package/dist/deviceManager.js.map +0 -1
  59. package/dist/frontend.d.ts +0 -241
  60. package/dist/frontend.d.ts.map +0 -1
  61. package/dist/frontend.js.map +0 -1
  62. package/dist/helpers.d.ts +0 -46
  63. package/dist/helpers.d.ts.map +0 -1
  64. package/dist/helpers.js.map +0 -1
  65. package/dist/index.d.ts +0 -36
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/index.js.map +0 -1
  68. package/dist/logger/export.d.ts +0 -2
  69. package/dist/logger/export.d.ts.map +0 -1
  70. package/dist/logger/export.js.map +0 -1
  71. package/dist/matter/behaviors.d.ts +0 -2
  72. package/dist/matter/behaviors.d.ts.map +0 -1
  73. package/dist/matter/behaviors.js.map +0 -1
  74. package/dist/matter/clusters.d.ts +0 -2
  75. package/dist/matter/clusters.d.ts.map +0 -1
  76. package/dist/matter/clusters.js.map +0 -1
  77. package/dist/matter/devices.d.ts +0 -2
  78. package/dist/matter/devices.d.ts.map +0 -1
  79. package/dist/matter/devices.js.map +0 -1
  80. package/dist/matter/endpoints.d.ts +0 -2
  81. package/dist/matter/endpoints.d.ts.map +0 -1
  82. package/dist/matter/endpoints.js.map +0 -1
  83. package/dist/matter/export.d.ts +0 -5
  84. package/dist/matter/export.d.ts.map +0 -1
  85. package/dist/matter/export.js.map +0 -1
  86. package/dist/matter/types.d.ts +0 -3
  87. package/dist/matter/types.d.ts.map +0 -1
  88. package/dist/matter/types.js.map +0 -1
  89. package/dist/matterbridge.d.ts +0 -435
  90. package/dist/matterbridge.d.ts.map +0 -1
  91. package/dist/matterbridge.js.map +0 -1
  92. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -40
  93. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  94. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  95. package/dist/matterbridgeBehaviors.d.ts +0 -1188
  96. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  97. package/dist/matterbridgeBehaviors.js.map +0 -1
  98. package/dist/matterbridgeDeviceTypes.d.ts +0 -494
  99. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  100. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  101. package/dist/matterbridgeDynamicPlatform.d.ts +0 -40
  102. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  103. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  104. package/dist/matterbridgeEndpoint.d.ts +0 -965
  105. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  106. package/dist/matterbridgeEndpoint.js.map +0 -1
  107. package/dist/matterbridgeEndpointHelpers.d.ts +0 -2728
  108. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  109. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  110. package/dist/matterbridgePlatform.d.ts +0 -294
  111. package/dist/matterbridgePlatform.d.ts.map +0 -1
  112. package/dist/matterbridgePlatform.js.map +0 -1
  113. package/dist/matterbridgeTypes.d.ts +0 -187
  114. package/dist/matterbridgeTypes.d.ts.map +0 -1
  115. package/dist/matterbridgeTypes.js.map +0 -1
  116. package/dist/pluginManager.d.ts +0 -273
  117. package/dist/pluginManager.d.ts.map +0 -1
  118. package/dist/pluginManager.js.map +0 -1
  119. package/dist/roboticVacuumCleaner.d.ts +0 -43
  120. package/dist/roboticVacuumCleaner.d.ts.map +0 -1
  121. package/dist/roboticVacuumCleaner.js.map +0 -1
  122. package/dist/shelly.d.ts +0 -153
  123. package/dist/shelly.d.ts.map +0 -1
  124. package/dist/shelly.js.map +0 -1
  125. package/dist/storage/export.d.ts +0 -2
  126. package/dist/storage/export.d.ts.map +0 -1
  127. package/dist/storage/export.js.map +0 -1
  128. package/dist/update.d.ts +0 -58
  129. package/dist/update.d.ts.map +0 -1
  130. package/dist/update.js.map +0 -1
  131. package/dist/utils/colorUtils.d.ts +0 -61
  132. package/dist/utils/colorUtils.d.ts.map +0 -1
  133. package/dist/utils/colorUtils.js.map +0 -1
  134. package/dist/utils/commandLine.d.ts +0 -58
  135. package/dist/utils/commandLine.d.ts.map +0 -1
  136. package/dist/utils/commandLine.js.map +0 -1
  137. package/dist/utils/copyDirectory.d.ts +0 -32
  138. package/dist/utils/copyDirectory.d.ts.map +0 -1
  139. package/dist/utils/copyDirectory.js.map +0 -1
  140. package/dist/utils/createZip.d.ts +0 -38
  141. package/dist/utils/createZip.d.ts.map +0 -1
  142. package/dist/utils/createZip.js.map +0 -1
  143. package/dist/utils/deepCopy.d.ts +0 -31
  144. package/dist/utils/deepCopy.d.ts.map +0 -1
  145. package/dist/utils/deepCopy.js.map +0 -1
  146. package/dist/utils/deepEqual.d.ts +0 -53
  147. package/dist/utils/deepEqual.d.ts.map +0 -1
  148. package/dist/utils/deepEqual.js.map +0 -1
  149. package/dist/utils/export.d.ts +0 -11
  150. package/dist/utils/export.d.ts.map +0 -1
  151. package/dist/utils/export.js.map +0 -1
  152. package/dist/utils/hex.d.ts +0 -48
  153. package/dist/utils/hex.d.ts.map +0 -1
  154. package/dist/utils/hex.js.map +0 -1
  155. package/dist/utils/isvalid.d.ts +0 -102
  156. package/dist/utils/isvalid.d.ts.map +0 -1
  157. package/dist/utils/isvalid.js.map +0 -1
  158. package/dist/utils/network.d.ts +0 -69
  159. package/dist/utils/network.d.ts.map +0 -1
  160. package/dist/utils/network.js.map +0 -1
  161. package/dist/utils/wait.d.ts +0 -51
  162. package/dist/utils/wait.d.ts.map +0 -1
  163. package/dist/utils/wait.js.map +0 -1
  164. /package/frontend/build/static/js/{main.f6e0f736.js.LICENSE.txt → main.2486c3e3.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,111 +1,28 @@
1
- /**
2
- * This file contains the class Frontend.
3
- *
4
- * @file frontend.ts
5
- * @author Luca Liguori
6
- * @date 2025-01-13
7
- * @version 1.0.2
8
- *
9
- * Copyright 2025, 2026, 2027 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
- // @matter
24
1
  import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
25
- // Node modules
26
2
  import { createServer } from 'node:http';
27
3
  import https from 'node:https';
28
4
  import os from 'node:os';
29
5
  import path from 'node:path';
30
6
  import { promises as fs } from 'node:fs';
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
10
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
37
- // Matterbridge
38
- import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/export.js';
11
+ import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout } from './utils/export.js';
39
12
  import { plg } from './matterbridgeTypes.js';
40
13
  import { hasParameter } from './utils/export.js';
41
14
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
42
- /**
43
- * Websocket message ID for logging.
44
- * @constant {number}
45
- */
46
15
  export const WS_ID_LOG = 0;
47
- /**
48
- * Websocket message ID indicating a refresh is needed.
49
- * @constant {number}
50
- */
51
16
  export const WS_ID_REFRESH_NEEDED = 1;
52
- /**
53
- * Websocket message ID indicating a restart is needed.
54
- * @constant {number}
55
- */
56
17
  export const WS_ID_RESTART_NEEDED = 2;
57
- /**
58
- * Websocket message ID indicating a cpu update.
59
- * @constant {number}
60
- */
61
18
  export const WS_ID_CPU_UPDATE = 3;
62
- /**
63
- * Websocket message ID indicating a memory update.
64
- * @constant {number}
65
- */
66
19
  export const WS_ID_MEMORY_UPDATE = 4;
67
- /**
68
- * Websocket message ID indicating an uptime update.
69
- * @constant {number}
70
- */
71
20
  export const WS_ID_UPTIME_UPDATE = 5;
72
- /**
73
- * Websocket message ID indicating a snackbar message.
74
- * @constant {number}
75
- */
76
21
  export const WS_ID_SNACKBAR = 6;
77
- /**
78
- * Websocket message ID indicating matterbridge has un update available.
79
- * @constant {number}
80
- */
81
22
  export const WS_ID_UPDATE_NEEDED = 7;
82
- /**
83
- * Websocket message ID indicating a state update.
84
- * @constant {number}
85
- */
86
23
  export const WS_ID_STATEUPDATE = 8;
87
- /**
88
- * Websocket message ID indicating to close a permanent snackbar message.
89
- * @constant {number}
90
- */
91
24
  export const WS_ID_CLOSE_SNACKBAR = 9;
92
- /**
93
- * Websocket message ID indicating a shelly system update.
94
- * check:
95
- * curl -k http://127.0.0.1:8101/api/updates/sys/check
96
- * perform:
97
- * curl -k http://127.0.0.1:8101/api/updates/sys/perform
98
- * @constant {number}
99
- */
100
25
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
101
- /**
102
- * Websocket message ID indicating a shelly main update.
103
- * check:
104
- * curl -k http://127.0.0.1:8101/api/updates/main/check
105
- * perform:
106
- * curl -k http://127.0.0.1:8101/api/updates/main/perform
107
- * @constant {number}
108
- */
109
26
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
110
27
  export class Frontend {
111
28
  matterbridge;
@@ -118,7 +35,7 @@ export class Frontend {
118
35
  webSocketServer;
119
36
  constructor(matterbridge) {
120
37
  this.matterbridge = matterbridge;
121
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
38
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
122
39
  }
123
40
  set logLevel(logLevel) {
124
41
  this.log.logLevel = logLevel;
@@ -126,43 +43,13 @@ export class Frontend {
126
43
  async start(port = 8283) {
127
44
  this.port = port;
128
45
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
129
- // Initialize multer with the upload directory
130
46
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
131
47
  await fs.mkdir(uploadDir, { recursive: true });
132
48
  const upload = multer({ dest: uploadDir });
133
- // Create the express app that serves the frontend
134
49
  this.expressApp = express();
135
- // Inject logging/debug wrapper for route/middleware registration
136
- /*
137
- const methods = ['get', 'post', 'put', 'delete', 'use'];
138
- for (const method of methods) {
139
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
- const original = (this.expressApp as any)[method].bind(this.expressApp);
141
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
- (this.expressApp as any)[method] = (path: any, ...rest: any) => {
143
- try {
144
- console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
145
- return original(path, ...rest);
146
- } catch (err) {
147
- console.error(`[ERROR] Failed to register route: ${path}`);
148
- throw err;
149
- }
150
- };
151
- }
152
- */
153
- // Log all requests to the server for debugging
154
- /*
155
- this.expressApp.use((req, res, next) => {
156
- this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
157
- next();
158
- });
159
- */
160
- // Serve static files from '/static' endpoint
161
50
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
162
51
  if (!hasParameter('ssl')) {
163
- // Create an HTTP server and attach the express app
164
52
  this.httpServer = createServer(this.expressApp);
165
- // Listen on the specified port
166
53
  if (hasParameter('ingress')) {
167
54
  this.httpServer.listen(this.port, '0.0.0.0', () => {
168
55
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -176,7 +63,6 @@ export class Frontend {
176
63
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
177
64
  });
178
65
  }
179
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
66
  this.httpServer.on('error', (error) => {
181
67
  this.log.error(`Frontend http server error listening on ${this.port}`);
182
68
  switch (error.code) {
@@ -192,7 +78,6 @@ export class Frontend {
192
78
  });
193
79
  }
194
80
  else {
195
- // Load the SSL certificate, the private key and optionally the CA certificate
196
81
  let cert;
197
82
  try {
198
83
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -220,9 +105,7 @@ export class Frontend {
220
105
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
221
106
  }
222
107
  const serverOptions = { cert, key, ca };
223
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
224
108
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
225
- // Listen on the specified port
226
109
  if (hasParameter('ingress')) {
227
110
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
228
111
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -236,7 +119,6 @@ export class Frontend {
236
119
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
237
120
  });
238
121
  }
239
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
122
  this.httpsServer.on('error', (error) => {
241
123
  this.log.error(`Frontend https server error listening on ${this.port}`);
242
124
  switch (error.code) {
@@ -253,18 +135,16 @@ export class Frontend {
253
135
  }
254
136
  if (this.initializeError)
255
137
  return;
256
- // Create a WebSocket server and attach it to the http or https server
257
138
  const wssPort = this.port;
258
139
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
259
140
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
260
141
  this.webSocketServer.on('connection', (ws, request) => {
261
142
  const clientIp = request.socket.remoteAddress;
262
- // Set the global logger callback for the WebSocketServer
263
- let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
264
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
265
- callbackLogLevel = "info" /* LogLevel.INFO */;
266
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
267
- callbackLogLevel = "debug" /* LogLevel.DEBUG */;
143
+ let callbackLogLevel = "notice";
144
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
145
+ callbackLogLevel = "info";
146
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
147
+ callbackLogLevel = "debug";
268
148
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
269
149
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
270
150
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -298,7 +178,6 @@ export class Frontend {
298
178
  this.webSocketServer.on('error', (ws, error) => {
299
179
  this.log.error(`WebSocketServer error: ${error}`);
300
180
  });
301
- // Subscribe to cli events
302
181
  const { cliEmitter } = await import('./cli.js');
303
182
  cliEmitter.removeAllListeners();
304
183
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -310,7 +189,6 @@ export class Frontend {
310
189
  cliEmitter.on('cpu', (cpuUsage) => {
311
190
  this.wssSendCpuUpdate(cpuUsage);
312
191
  });
313
- // Endpoint to validate login code
314
192
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
315
193
  const { password } = req.body;
316
194
  this.log.debug('The frontend sent /api/login', password);
@@ -329,27 +207,23 @@ export class Frontend {
329
207
  this.log.warn('/api/login error wrong password');
330
208
  res.json({ valid: false });
331
209
  }
332
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
333
210
  }
334
211
  catch (error) {
335
212
  this.log.error('/api/login error getting password');
336
213
  res.json({ valid: false });
337
214
  }
338
215
  });
339
- // Endpoint to provide health check for docker
340
216
  this.expressApp.get('/health', (req, res) => {
341
217
  this.log.debug('Express received /health');
342
218
  const healthStatus = {
343
- status: 'ok', // Indicate service is healthy
344
- uptime: process.uptime(), // Server uptime in seconds
345
- timestamp: new Date().toISOString(), // Current timestamp
219
+ status: 'ok',
220
+ uptime: process.uptime(),
221
+ timestamp: new Date().toISOString(),
346
222
  };
347
223
  res.status(200).json(healthStatus);
348
224
  });
349
- // Endpoint to provide memory usage details
350
225
  this.expressApp.get('/memory', async (req, res) => {
351
226
  this.log.debug('Express received /memory');
352
- // Memory usage from process
353
227
  const memoryUsageRaw = process.memoryUsage();
354
228
  const memoryUsage = {
355
229
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -358,13 +232,10 @@ export class Frontend {
358
232
  external: this.formatMemoryUsage(memoryUsageRaw.external),
359
233
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
360
234
  };
361
- // V8 heap statistics
362
235
  const { default: v8 } = await import('node:v8');
363
236
  const heapStatsRaw = v8.getHeapStatistics();
364
237
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
365
- // Format heapStats
366
238
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
367
- // Format heapSpaces
368
239
  const heapSpaces = heapSpacesRaw.map((space) => ({
369
240
  ...space,
370
241
  space_size: this.formatMemoryUsage(space.space_size),
@@ -382,23 +253,19 @@ export class Frontend {
382
253
  };
383
254
  res.status(200).json(memoryReport);
384
255
  });
385
- // Endpoint to provide settings
386
256
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
387
257
  this.log.debug('The frontend sent /api/settings');
388
258
  res.json(await this.getApiSettings());
389
259
  });
390
- // Endpoint to provide plugins
391
260
  this.expressApp.get('/api/plugins', async (req, res) => {
392
261
  this.log.debug('The frontend sent /api/plugins');
393
262
  res.json(this.getBaseRegisteredPlugins());
394
263
  });
395
- // Endpoint to provide devices
396
264
  this.expressApp.get('/api/devices', async (req, res) => {
397
265
  this.log.debug('The frontend sent /api/devices');
398
266
  const devices = await this.getDevices();
399
267
  res.json(devices);
400
268
  });
401
- // Endpoint to view the matterbridge log
402
269
  this.expressApp.get('/api/view-mblog', async (req, res) => {
403
270
  this.log.debug('The frontend sent /api/view-mblog');
404
271
  try {
@@ -411,7 +278,6 @@ export class Frontend {
411
278
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
412
279
  }
413
280
  });
414
- // Endpoint to view the matter.js log
415
281
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
416
282
  this.log.debug('The frontend sent /api/view-mjlog');
417
283
  try {
@@ -424,7 +290,6 @@ export class Frontend {
424
290
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
425
291
  }
426
292
  });
427
- // Endpoint to view the shelly log
428
293
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
429
294
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
430
295
  try {
@@ -437,7 +302,6 @@ export class Frontend {
437
302
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
438
303
  }
439
304
  });
440
- // Endpoint to download the matterbridge log
441
305
  this.expressApp.get('/api/download-mblog', async (req, res) => {
442
306
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
443
307
  try {
@@ -450,7 +314,6 @@ export class Frontend {
450
314
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
451
315
  }
452
316
  res.type('text/plain');
453
- // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
454
317
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
455
318
  if (error) {
456
319
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -458,7 +321,6 @@ export class Frontend {
458
321
  }
459
322
  });
460
323
  });
461
- // Endpoint to download the matter log
462
324
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
463
325
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
464
326
  try {
@@ -478,7 +340,6 @@ export class Frontend {
478
340
  }
479
341
  });
480
342
  });
481
- // Endpoint to download the shelly log
482
343
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
483
344
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
484
345
  try {
@@ -498,7 +359,6 @@ export class Frontend {
498
359
  }
499
360
  });
500
361
  });
501
- // Endpoint to download the matterbridge storage directory
502
362
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
503
363
  this.log.debug('The frontend sent /api/download-mbstorage');
504
364
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -509,7 +369,6 @@ export class Frontend {
509
369
  }
510
370
  });
511
371
  });
512
- // Endpoint to download the matter storage file
513
372
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
514
373
  this.log.debug('The frontend sent /api/download-mjstorage');
515
374
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -520,7 +379,6 @@ export class Frontend {
520
379
  }
521
380
  });
522
381
  });
523
- // Endpoint to download the matterbridge plugin directory
524
382
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
525
383
  this.log.debug('The frontend sent /api/download-pluginstorage');
526
384
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -531,7 +389,6 @@ export class Frontend {
531
389
  }
532
390
  });
533
391
  });
534
- // Endpoint to download the matterbridge plugin config files
535
392
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
536
393
  this.log.debug('The frontend sent /api/download-pluginconfig');
537
394
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
@@ -542,7 +399,6 @@ export class Frontend {
542
399
  }
543
400
  });
544
401
  });
545
- // Endpoint to download the matterbridge backup (created with the backup command)
546
402
  this.expressApp.get('/api/download-backup', async (req, res) => {
547
403
  this.log.debug('The frontend sent /api/download-backup');
548
404
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -552,7 +408,6 @@ export class Frontend {
552
408
  }
553
409
  });
554
410
  });
555
- // Endpoint to upload a package
556
411
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
557
412
  const { filename } = req.body;
558
413
  const file = req.file;
@@ -562,13 +417,10 @@ export class Frontend {
562
417
  return;
563
418
  }
564
419
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
565
- // Define the path where the plugin file will be saved
566
420
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
567
421
  try {
568
- // Move the uploaded file to the specified path
569
422
  await fs.rename(file.path, filePath);
570
423
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
571
- // Install the plugin package
572
424
  if (filename.endsWith('.tgz')) {
573
425
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
574
426
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -587,7 +439,6 @@ export class Frontend {
587
439
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
588
440
  }
589
441
  });
590
- // Fallback for routing (must be the last route)
591
442
  this.expressApp.use((req, res) => {
592
443
  this.log.debug('The frontend sent:', req.url);
593
444
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -595,61 +446,66 @@ export class Frontend {
595
446
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
596
447
  }
597
448
  async stop() {
598
- // Close the http server
599
- if (this.httpServer) {
600
- this.httpServer.close((error) => {
601
- if (error) {
602
- this.log.error(`Error closing http server: ${error}`);
603
- }
604
- else {
605
- this.log.debug('Http server closed successfully');
606
- }
607
- });
608
- this.httpServer.removeAllListeners();
609
- this.httpServer = undefined;
610
- this.log.debug('Frontend http server closed successfully');
611
- }
612
- // Close the https server
613
- if (this.httpsServer) {
614
- this.httpsServer.close((error) => {
615
- if (error) {
616
- this.log.error(`Error closing https server: ${error}`);
617
- }
618
- else {
619
- this.log.debug('Https server closed successfully');
620
- }
621
- });
622
- this.httpsServer.removeAllListeners();
623
- this.httpsServer = undefined;
624
- this.log.debug('Frontend https server closed successfully');
625
- }
626
- // Remove listeners from the express app
449
+ this.log.debug('Stopping the frontend...');
627
450
  if (this.expressApp) {
628
451
  this.expressApp.removeAllListeners();
629
452
  this.expressApp = undefined;
630
453
  this.log.debug('Frontend app closed successfully');
631
454
  }
632
- // Close the WebSocket server
633
455
  if (this.webSocketServer) {
634
- // Close all active connections
635
456
  this.webSocketServer.clients.forEach((client) => {
636
457
  if (client.readyState === WebSocket.OPEN) {
637
458
  client.close();
638
459
  }
639
460
  });
640
- this.webSocketServer.close((error) => {
641
- if (error) {
642
- this.log.error(`Error closing WebSocket server: ${error}`);
643
- }
644
- else {
645
- this.log.debug('WebSocket server closed successfully');
646
- }
647
- });
648
- // this.webSocketServer.removeAllListeners();
461
+ await withTimeout(new Promise((resolve) => {
462
+ this.webSocketServer?.close((error) => {
463
+ if (error) {
464
+ this.log.error(`Error closing WebSocket server: ${error}`);
465
+ }
466
+ else {
467
+ this.log.debug('WebSocket server closed successfully');
468
+ }
469
+ resolve();
470
+ });
471
+ }), 10000, false);
472
+ this.webSocketServer.removeAllListeners();
649
473
  this.webSocketServer = undefined;
650
474
  }
475
+ if (this.httpServer) {
476
+ await withTimeout(new Promise((resolve) => {
477
+ this.httpServer?.close((error) => {
478
+ if (error) {
479
+ this.log.error(`Error closing http server: ${error}`);
480
+ }
481
+ else {
482
+ this.log.debug('Http server closed successfully');
483
+ }
484
+ resolve();
485
+ });
486
+ }), 10000, false);
487
+ this.httpServer.removeAllListeners();
488
+ this.httpServer = undefined;
489
+ this.log.debug('Frontend http server closed successfully');
490
+ }
491
+ if (this.httpsServer) {
492
+ await withTimeout(new Promise((resolve) => {
493
+ this.httpsServer?.close((error) => {
494
+ if (error) {
495
+ this.log.error(`Error closing https server: ${error}`);
496
+ }
497
+ else {
498
+ this.log.debug('Https server closed successfully');
499
+ }
500
+ resolve();
501
+ });
502
+ }), 10000, false);
503
+ this.httpsServer.removeAllListeners();
504
+ this.httpsServer = undefined;
505
+ this.log.debug('Frontend https server closed successfully');
506
+ }
507
+ this.log.debug('Frontend stopped successfully');
651
508
  }
652
- // Function to format bytes to KB, MB, or GB
653
509
  formatMemoryUsage = (bytes) => {
654
510
  if (bytes >= 1024 ** 3) {
655
511
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -661,7 +517,6 @@ export class Frontend {
661
517
  return `${(bytes / 1024).toFixed(2)} KB`;
662
518
  }
663
519
  };
664
- // Function to format system uptime with only the most significant unit
665
520
  formatOsUpTime = (seconds) => {
666
521
  if (seconds >= 86400) {
667
522
  const days = Math.floor(seconds / 86400);
@@ -677,14 +532,8 @@ export class Frontend {
677
532
  }
678
533
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
679
534
  };
680
- /**
681
- * Retrieves the api settings data.
682
- *
683
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
684
- */
685
535
  async getApiSettings() {
686
536
  const { lastCpuUsage } = await import('./cli.js');
687
- // Update the system information
688
537
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
689
538
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
690
539
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -693,7 +542,6 @@ export class Frontend {
693
542
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
694
543
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
695
544
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
696
- // Update the matterbridge information
697
545
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
698
546
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
699
547
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -712,11 +560,6 @@ export class Frontend {
712
560
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
713
561
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
714
562
  }
715
- /**
716
- * Retrieves the reachable attribute.
717
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
718
- * @returns {boolean} The reachable attribute.
719
- */
720
563
  getReachability(device) {
721
564
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
722
565
  return false;
@@ -741,20 +584,13 @@ export class Frontend {
741
584
  }
742
585
  return;
743
586
  };
744
- // Root endpoint
745
587
  if (device.hasClusterServer(PowerSource.Cluster.id))
746
588
  return powerSource(device);
747
- // Child endpoints
748
589
  for (const child of device.getChildEndpoints()) {
749
590
  if (child.hasClusterServer(PowerSource.Cluster.id))
750
591
  return powerSource(child);
751
592
  }
752
593
  }
753
- /**
754
- * Retrieves the cluster text description from a given device.
755
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
756
- * @returns {string} The attributes description of the cluster servers in the device.
757
- */
758
594
  getClusterTextFromDevice(device) {
759
595
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
760
596
  return '';
@@ -796,7 +632,6 @@ export class Frontend {
796
632
  let attributes = '';
797
633
  let supportedModes = [];
798
634
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
799
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
800
635
  if (typeof attributeValue === 'undefined')
801
636
  return;
802
637
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -888,13 +723,8 @@ export class Frontend {
888
723
  if (clusterName === 'userLabel' && attributeName === 'labelList')
889
724
  attributes += `${getUserLabel(device)} `;
890
725
  });
891
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
892
726
  return attributes.trimStart().trimEnd();
893
727
  }
894
- /**
895
- * Retrieves the base registered plugins sanitized for res.json().
896
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
897
- */
898
728
  getBaseRegisteredPlugins() {
899
729
  const baseRegisteredPlugins = [];
900
730
  for (const plugin of this.matterbridge.plugins) {
@@ -933,18 +763,11 @@ export class Frontend {
933
763
  }
934
764
  return baseRegisteredPlugins;
935
765
  }
936
- /**
937
- * Retrieves the devices from Matterbridge.
938
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
939
- * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
940
- */
941
766
  async getDevices(pluginName) {
942
767
  const devices = [];
943
768
  this.matterbridge.devices.forEach(async (device) => {
944
- // Filter by pluginName if provided
945
769
  if (pluginName && pluginName !== device.plugin)
946
770
  return;
947
- // Check if the device has the required properties
948
771
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
949
772
  return;
950
773
  const cluster = this.getClusterTextFromDevice(device);
@@ -964,13 +787,6 @@ export class Frontend {
964
787
  });
965
788
  return devices;
966
789
  }
967
- /**
968
- * Handles incoming websocket messages for the Matterbridge frontend.
969
- *
970
- * @param {WebSocket} client - The websocket client that sent the message.
971
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
972
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
973
- */
974
790
  async wsMessageHandler(client, message) {
975
791
  let data;
976
792
  try {
@@ -1017,10 +833,8 @@ export class Frontend {
1017
833
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
1018
834
  const packageName = data.params.packageName.replace(/@.*$/, '');
1019
835
  if (data.params.restart === false && packageName !== 'matterbridge') {
1020
- // The install comes from InstallPlugins
1021
836
  this.matterbridge.plugins.add(packageName).then((plugin) => {
1022
837
  if (plugin) {
1023
- // The plugin is not registered
1024
838
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1025
839
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
1026
840
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
@@ -1028,7 +842,6 @@ export class Frontend {
1028
842
  });
1029
843
  }
1030
844
  else {
1031
- // The plugin is already registered
1032
845
  this.wssSendSnackbarMessage(`Restart required`, 0);
1033
846
  this.wssSendRefreshRequired('plugins');
1034
847
  this.wssSendRestartRequired();
@@ -1036,7 +849,6 @@ export class Frontend {
1036
849
  });
1037
850
  }
1038
851
  else {
1039
- // The package is matterbridge
1040
852
  if (this.matterbridge.restartMode !== '') {
1041
853
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1042
854
  this.matterbridge.shutdownProcess();
@@ -1058,7 +870,6 @@ export class Frontend {
1058
870
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1059
871
  return;
1060
872
  }
1061
- // The package is a plugin
1062
873
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1063
874
  if (plugin) {
1064
875
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -1067,7 +878,6 @@ export class Frontend {
1067
878
  this.wssSendRefreshRequired('plugins');
1068
879
  this.wssSendRefreshRequired('devices');
1069
880
  }
1070
- // Uninstall the package
1071
881
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1072
882
  this.matterbridge
1073
883
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1344,7 +1154,6 @@ export class Frontend {
1344
1154
  });
1345
1155
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1346
1156
  deviceTypes = [];
1347
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1348
1157
  const name = childEndpoint.endpoint?.id;
1349
1158
  const clusterServers = childEndpoint.getAllClusterServers();
1350
1159
  clusterServers.forEach((clusterServer) => {
@@ -1458,22 +1267,22 @@ export class Frontend {
1458
1267
  if (isValidString(data.params.value, 4)) {
1459
1268
  this.log.debug('Matterbridge logger level:', data.params.value);
1460
1269
  if (data.params.value === 'Debug') {
1461
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1270
+ await this.matterbridge.setLogLevel("debug");
1462
1271
  }
1463
1272
  else if (data.params.value === 'Info') {
1464
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1273
+ await this.matterbridge.setLogLevel("info");
1465
1274
  }
1466
1275
  else if (data.params.value === 'Notice') {
1467
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1276
+ await this.matterbridge.setLogLevel("notice");
1468
1277
  }
1469
1278
  else if (data.params.value === 'Warn') {
1470
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1279
+ await this.matterbridge.setLogLevel("warn");
1471
1280
  }
1472
1281
  else if (data.params.value === 'Error') {
1473
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1282
+ await this.matterbridge.setLogLevel("error");
1474
1283
  }
1475
1284
  else if (data.params.value === 'Fatal') {
1476
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1285
+ await this.matterbridge.setLogLevel("fatal");
1477
1286
  }
1478
1287
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1479
1288
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1484,7 +1293,6 @@ export class Frontend {
1484
1293
  this.log.debug('Matterbridge file log:', data.params.value);
1485
1294
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1486
1295
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1487
- // Create the file logger for matterbridge
1488
1296
  if (data.params.value)
1489
1297
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1490
1298
  else
@@ -1623,6 +1431,15 @@ export class Frontend {
1623
1431
  }
1624
1432
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1625
1433
  break;
1434
+ case 'setvirtualmode':
1435
+ if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
1436
+ this.matterbridge.matterbridgeInformation.virtualMode = data.params.value;
1437
+ this.log.debug(`Set matterbridge virtual mode to ${CYAN}${data.params.value}${db}`);
1438
+ await this.matterbridge.nodeContext?.set('virtualmode', data.params.value);
1439
+ this.wssSendRestartRequired();
1440
+ }
1441
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1442
+ break;
1626
1443
  default:
1627
1444
  this.log.warn(`Unknown parameter ${data.params.name} in /api/config`);
1628
1445
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Unknown parameter ${data.params.name} in /api/config` }));
@@ -1640,19 +1457,15 @@ export class Frontend {
1640
1457
  return;
1641
1458
  }
1642
1459
  const config = plugin.configJson;
1643
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1644
1460
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1645
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1646
1461
  if (select === 'serial')
1647
1462
  this.log.info(`Selected device serial ${data.params.serial}`);
1648
1463
  if (select === 'name')
1649
1464
  this.log.info(`Selected device name ${data.params.name}`);
1650
1465
  if (config && select && (select === 'serial' || select === 'name')) {
1651
- // Remove postfix from the serial if it exists
1652
1466
  if (config.postfix) {
1653
1467
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1654
1468
  }
1655
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1656
1469
  if (isValidArray(config.whiteList, 1)) {
1657
1470
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1658
1471
  config.whiteList.push(data.params.serial);
@@ -1661,7 +1474,6 @@ export class Frontend {
1661
1474
  config.whiteList.push(data.params.name);
1662
1475
  }
1663
1476
  }
1664
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1665
1477
  if (isValidArray(config.blackList, 1)) {
1666
1478
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1667
1479
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1691,9 +1503,7 @@ export class Frontend {
1691
1503
  return;
1692
1504
  }
1693
1505
  const config = plugin.configJson;
1694
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1695
1506
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1696
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1697
1507
  if (select === 'serial')
1698
1508
  this.log.info(`Unselected device serial ${data.params.serial}`);
1699
1509
  if (select === 'name')
@@ -1702,7 +1512,6 @@ export class Frontend {
1702
1512
  if (config.postfix) {
1703
1513
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1704
1514
  }
1705
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1706
1515
  if (isValidArray(config.whiteList, 1)) {
1707
1516
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1708
1517
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1711,7 +1520,6 @@ export class Frontend {
1711
1520
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1712
1521
  }
1713
1522
  }
1714
- // Add the serial to the blackList
1715
1523
  if (isValidArray(config.blackList)) {
1716
1524
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1717
1525
  config.blackList.push(data.params.serial);
@@ -1744,219 +1552,114 @@ export class Frontend {
1744
1552
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1745
1553
  }
1746
1554
  }
1747
- /**
1748
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1749
- *
1750
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1751
- * @param {string} time - The time string of the message
1752
- * @param {string} name - The logger name of the message
1753
- * @param {string} message - The content of the message.
1754
- *
1755
- * @remark
1756
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1757
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1758
- * The function sends the message to all connected clients.
1759
- */
1760
1555
  wssSendMessage(level, time, name, message) {
1761
1556
  if (!level || !time || !name || !message)
1762
1557
  return;
1763
- // Remove ANSI escape codes from the message
1764
- // eslint-disable-next-line no-control-regex
1765
1558
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1766
- // Remove leading asterisks from the message
1767
1559
  message = message.replace(/^\*+/, '');
1768
- // Replace all occurrences of \t and \n
1769
1560
  message = message.replace(/[\t\n]/g, '');
1770
- // Remove non-printable characters
1771
- // eslint-disable-next-line no-control-regex
1772
1561
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1773
- // Replace all occurrences of \" with "
1774
1562
  message = message.replace(/\\"/g, '"');
1775
- // Replace all occurrences of angle-brackets with &lt; and &gt;"
1776
1563
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1777
- // Define the maximum allowed length for continuous characters without a space
1778
1564
  const maxContinuousLength = 100;
1779
1565
  const keepStartLength = 20;
1780
1566
  const keepEndLength = 20;
1781
- // Split the message into words
1782
1567
  message = message
1783
1568
  .split(' ')
1784
1569
  .map((word) => {
1785
- // If the word length exceeds the max continuous length, insert spaces and truncate
1786
1570
  if (word.length > maxContinuousLength) {
1787
1571
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1788
1572
  }
1789
1573
  return word;
1790
1574
  })
1791
1575
  .join(' ');
1792
- // Send the message to all connected clients
1793
1576
  this.webSocketServer?.clients.forEach((client) => {
1794
1577
  if (client.readyState === WebSocket.OPEN) {
1795
1578
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1796
1579
  }
1797
1580
  });
1798
1581
  }
1799
- /**
1800
- * Sends a need to refresh WebSocket message to all connected clients.
1801
- *
1802
- * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1803
- * possible values:
1804
- * - 'matterbridgeLatestVersion'
1805
- * - 'matterbridgeAdvertise'
1806
- * - 'online'
1807
- * - 'offline'
1808
- * - 'reachability'
1809
- * - 'settings'
1810
- * - 'plugins'
1811
- * - 'pluginsRestart'
1812
- * - 'devices'
1813
- * - 'fabrics'
1814
- * - 'sessions'
1815
- */
1816
1582
  wssSendRefreshRequired(changed = null) {
1817
1583
  this.log.debug('Sending a refresh required message to all connected clients');
1818
- // Send the message to all connected clients
1819
1584
  this.webSocketServer?.clients.forEach((client) => {
1820
1585
  if (client.readyState === WebSocket.OPEN) {
1821
1586
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1822
1587
  }
1823
1588
  });
1824
1589
  }
1825
- /**
1826
- * Sends a need to restart WebSocket message to all connected clients.
1827
- *
1828
- */
1829
1590
  wssSendRestartRequired(snackbar = true) {
1830
1591
  this.log.debug('Sending a restart required message to all connected clients');
1831
1592
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1832
1593
  if (snackbar === true)
1833
1594
  this.wssSendSnackbarMessage(`Restart required`, 0);
1834
- // Send the message to all connected clients
1835
1595
  this.webSocketServer?.clients.forEach((client) => {
1836
1596
  if (client.readyState === WebSocket.OPEN) {
1837
1597
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1838
1598
  }
1839
1599
  });
1840
1600
  }
1841
- /**
1842
- * Sends a need to update WebSocket message to all connected clients.
1843
- *
1844
- */
1845
1601
  wssSendUpdateRequired() {
1846
1602
  this.log.debug('Sending an update required message to all connected clients');
1847
1603
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1848
- // Send the message to all connected clients
1849
1604
  this.webSocketServer?.clients.forEach((client) => {
1850
1605
  if (client.readyState === WebSocket.OPEN) {
1851
1606
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1852
1607
  }
1853
1608
  });
1854
1609
  }
1855
- /**
1856
- * Sends a cpu update message to all connected clients.
1857
- *
1858
- */
1859
1610
  wssSendCpuUpdate(cpuUsage) {
1860
1611
  if (hasParameter('debug'))
1861
1612
  this.log.debug('Sending a cpu update message to all connected clients');
1862
- // Send the message to all connected clients
1863
1613
  this.webSocketServer?.clients.forEach((client) => {
1864
1614
  if (client.readyState === WebSocket.OPEN) {
1865
1615
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1866
1616
  }
1867
1617
  });
1868
1618
  }
1869
- /**
1870
- * Sends a memory update message to all connected clients.
1871
- *
1872
- */
1873
1619
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1874
1620
  if (hasParameter('debug'))
1875
1621
  this.log.debug('Sending a memory update message to all connected clients');
1876
- // Send the message to all connected clients
1877
1622
  this.webSocketServer?.clients.forEach((client) => {
1878
1623
  if (client.readyState === WebSocket.OPEN) {
1879
1624
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1880
1625
  }
1881
1626
  });
1882
1627
  }
1883
- /**
1884
- * Sends an uptime update message to all connected clients.
1885
- *
1886
- */
1887
1628
  wssSendUptimeUpdate(systemUptime, processUptime) {
1888
1629
  if (hasParameter('debug'))
1889
1630
  this.log.debug('Sending a uptime update message to all connected clients');
1890
- // Send the message to all connected clients
1891
1631
  this.webSocketServer?.clients.forEach((client) => {
1892
1632
  if (client.readyState === WebSocket.OPEN) {
1893
1633
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1894
1634
  }
1895
1635
  });
1896
1636
  }
1897
- /**
1898
- * Sends an open snackbar message to all connected clients.
1899
- * @param {string} message - The message to send.
1900
- * @param {number} timeout - The timeout in seconds for the snackbar message.
1901
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1902
- *
1903
- */
1904
1637
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1905
1638
  this.log.debug('Sending a snackbar message to all connected clients');
1906
- // Send the message to all connected clients
1907
1639
  this.webSocketServer?.clients.forEach((client) => {
1908
1640
  if (client.readyState === WebSocket.OPEN) {
1909
1641
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1910
1642
  }
1911
1643
  });
1912
1644
  }
1913
- /**
1914
- * Sends a close snackbar message to all connected clients.
1915
- * @param {string} message - The message to send.
1916
- *
1917
- */
1918
1645
  wssSendCloseSnackbarMessage(message) {
1919
1646
  this.log.debug('Sending a close snackbar message to all connected clients');
1920
- // Send the message to all connected clients
1921
1647
  this.webSocketServer?.clients.forEach((client) => {
1922
1648
  if (client.readyState === WebSocket.OPEN) {
1923
1649
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1924
1650
  }
1925
1651
  });
1926
1652
  }
1927
- /**
1928
- * Sends an attribute update message to all connected WebSocket clients.
1929
- *
1930
- * @param {string | undefined} plugin - The name of the plugin.
1931
- * @param {string | undefined} serialNumber - The serial number of the device.
1932
- * @param {string | undefined} uniqueId - The unique identifier of the device.
1933
- * @param {string} cluster - The cluster name where the attribute belongs.
1934
- * @param {string} attribute - The name of the attribute that changed.
1935
- * @param {number | string | boolean} value - The new value of the attribute.
1936
- *
1937
- * @remarks
1938
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1939
- * with the updated attribute information.
1940
- */
1941
1653
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1942
1654
  this.log.debug('Sending an attribute update message to all connected clients');
1943
- // Send the message to all connected clients
1944
1655
  this.webSocketServer?.clients.forEach((client) => {
1945
1656
  if (client.readyState === WebSocket.OPEN) {
1946
1657
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1947
1658
  }
1948
1659
  });
1949
1660
  }
1950
- /**
1951
- * Sends a message to all connected clients.
1952
- * @param {number} id - The message id.
1953
- * @param {string} method - The message method.
1954
- * @param {Record<string, string | number | boolean>} params - The message parameters.
1955
- *
1956
- */
1957
1661
  wssBroadcastMessage(id, method, params) {
1958
1662
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1959
- // Send the message to all connected clients
1960
1663
  this.webSocketServer?.clients.forEach((client) => {
1961
1664
  if (client.readyState === WebSocket.OPEN) {
1962
1665
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1964,4 +1667,3 @@ export class Frontend {
1964
1667
  });
1965
1668
  }
1966
1669
  }
1967
- //# sourceMappingURL=frontend.js.map