matterbridge 3.0.2 → 3.0.3-dev-20250517-bcc5d13

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 (159) hide show
  1. package/CHANGELOG.md +20 -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 +62 -369
  7. package/dist/helpers.js +0 -49
  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 +134 -873
  17. package/dist/matterbridgeAccessoryPlatform.js +0 -34
  18. package/dist/matterbridgeBehaviors.js +4 -53
  19. package/dist/matterbridgeDeviceTypes.js +12 -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/npm-shrinkwrap.json +2 -2
  42. package/package.json +1 -2
  43. package/dist/cli.d.ts +0 -29
  44. package/dist/cli.d.ts.map +0 -1
  45. package/dist/cli.js.map +0 -1
  46. package/dist/cluster/export.d.ts +0 -2
  47. package/dist/cluster/export.d.ts.map +0 -1
  48. package/dist/cluster/export.js.map +0 -1
  49. package/dist/defaultConfigSchema.d.ts +0 -27
  50. package/dist/defaultConfigSchema.d.ts.map +0 -1
  51. package/dist/defaultConfigSchema.js.map +0 -1
  52. package/dist/deviceManager.d.ts +0 -114
  53. package/dist/deviceManager.d.ts.map +0 -1
  54. package/dist/deviceManager.js.map +0 -1
  55. package/dist/frontend.d.ts +0 -241
  56. package/dist/frontend.d.ts.map +0 -1
  57. package/dist/frontend.js.map +0 -1
  58. package/dist/helpers.d.ts +0 -46
  59. package/dist/helpers.d.ts.map +0 -1
  60. package/dist/helpers.js.map +0 -1
  61. package/dist/index.d.ts +0 -36
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/logger/export.d.ts +0 -2
  65. package/dist/logger/export.d.ts.map +0 -1
  66. package/dist/logger/export.js.map +0 -1
  67. package/dist/matter/behaviors.d.ts +0 -2
  68. package/dist/matter/behaviors.d.ts.map +0 -1
  69. package/dist/matter/behaviors.js.map +0 -1
  70. package/dist/matter/clusters.d.ts +0 -2
  71. package/dist/matter/clusters.d.ts.map +0 -1
  72. package/dist/matter/clusters.js.map +0 -1
  73. package/dist/matter/devices.d.ts +0 -2
  74. package/dist/matter/devices.d.ts.map +0 -1
  75. package/dist/matter/devices.js.map +0 -1
  76. package/dist/matter/endpoints.d.ts +0 -2
  77. package/dist/matter/endpoints.d.ts.map +0 -1
  78. package/dist/matter/endpoints.js.map +0 -1
  79. package/dist/matter/export.d.ts +0 -5
  80. package/dist/matter/export.d.ts.map +0 -1
  81. package/dist/matter/export.js.map +0 -1
  82. package/dist/matter/types.d.ts +0 -3
  83. package/dist/matter/types.d.ts.map +0 -1
  84. package/dist/matter/types.js.map +0 -1
  85. package/dist/matterbridge.d.ts +0 -435
  86. package/dist/matterbridge.d.ts.map +0 -1
  87. package/dist/matterbridge.js.map +0 -1
  88. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -40
  89. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  90. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  91. package/dist/matterbridgeBehaviors.d.ts +0 -1188
  92. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  93. package/dist/matterbridgeBehaviors.js.map +0 -1
  94. package/dist/matterbridgeDeviceTypes.d.ts +0 -494
  95. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  96. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  97. package/dist/matterbridgeDynamicPlatform.d.ts +0 -40
  98. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  99. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  100. package/dist/matterbridgeEndpoint.d.ts +0 -965
  101. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  102. package/dist/matterbridgeEndpoint.js.map +0 -1
  103. package/dist/matterbridgeEndpointHelpers.d.ts +0 -2728
  104. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  105. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  106. package/dist/matterbridgePlatform.d.ts +0 -294
  107. package/dist/matterbridgePlatform.d.ts.map +0 -1
  108. package/dist/matterbridgePlatform.js.map +0 -1
  109. package/dist/matterbridgeTypes.d.ts +0 -187
  110. package/dist/matterbridgeTypes.d.ts.map +0 -1
  111. package/dist/matterbridgeTypes.js.map +0 -1
  112. package/dist/pluginManager.d.ts +0 -273
  113. package/dist/pluginManager.d.ts.map +0 -1
  114. package/dist/pluginManager.js.map +0 -1
  115. package/dist/roboticVacuumCleaner.d.ts +0 -43
  116. package/dist/roboticVacuumCleaner.d.ts.map +0 -1
  117. package/dist/roboticVacuumCleaner.js.map +0 -1
  118. package/dist/shelly.d.ts +0 -153
  119. package/dist/shelly.d.ts.map +0 -1
  120. package/dist/shelly.js.map +0 -1
  121. package/dist/storage/export.d.ts +0 -2
  122. package/dist/storage/export.d.ts.map +0 -1
  123. package/dist/storage/export.js.map +0 -1
  124. package/dist/update.d.ts +0 -58
  125. package/dist/update.d.ts.map +0 -1
  126. package/dist/update.js.map +0 -1
  127. package/dist/utils/colorUtils.d.ts +0 -61
  128. package/dist/utils/colorUtils.d.ts.map +0 -1
  129. package/dist/utils/colorUtils.js.map +0 -1
  130. package/dist/utils/commandLine.d.ts +0 -58
  131. package/dist/utils/commandLine.d.ts.map +0 -1
  132. package/dist/utils/commandLine.js.map +0 -1
  133. package/dist/utils/copyDirectory.d.ts +0 -32
  134. package/dist/utils/copyDirectory.d.ts.map +0 -1
  135. package/dist/utils/copyDirectory.js.map +0 -1
  136. package/dist/utils/createZip.d.ts +0 -38
  137. package/dist/utils/createZip.d.ts.map +0 -1
  138. package/dist/utils/createZip.js.map +0 -1
  139. package/dist/utils/deepCopy.d.ts +0 -31
  140. package/dist/utils/deepCopy.d.ts.map +0 -1
  141. package/dist/utils/deepCopy.js.map +0 -1
  142. package/dist/utils/deepEqual.d.ts +0 -53
  143. package/dist/utils/deepEqual.d.ts.map +0 -1
  144. package/dist/utils/deepEqual.js.map +0 -1
  145. package/dist/utils/export.d.ts +0 -11
  146. package/dist/utils/export.d.ts.map +0 -1
  147. package/dist/utils/export.js.map +0 -1
  148. package/dist/utils/hex.d.ts +0 -48
  149. package/dist/utils/hex.d.ts.map +0 -1
  150. package/dist/utils/hex.js.map +0 -1
  151. package/dist/utils/isvalid.d.ts +0 -102
  152. package/dist/utils/isvalid.d.ts.map +0 -1
  153. package/dist/utils/isvalid.js.map +0 -1
  154. package/dist/utils/network.d.ts +0 -69
  155. package/dist/utils/network.d.ts.map +0 -1
  156. package/dist/utils/network.js.map +0 -1
  157. package/dist/utils/wait.d.ts +0 -51
  158. package/dist/utils/wait.d.ts.map +0 -1
  159. package/dist/utils/wait.js.map +0 -1
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
@@ -1640,19 +1448,15 @@ export class Frontend {
1640
1448
  return;
1641
1449
  }
1642
1450
  const config = plugin.configJson;
1643
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1644
1451
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1645
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1646
1452
  if (select === 'serial')
1647
1453
  this.log.info(`Selected device serial ${data.params.serial}`);
1648
1454
  if (select === 'name')
1649
1455
  this.log.info(`Selected device name ${data.params.name}`);
1650
1456
  if (config && select && (select === 'serial' || select === 'name')) {
1651
- // Remove postfix from the serial if it exists
1652
1457
  if (config.postfix) {
1653
1458
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1654
1459
  }
1655
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1656
1460
  if (isValidArray(config.whiteList, 1)) {
1657
1461
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1658
1462
  config.whiteList.push(data.params.serial);
@@ -1661,7 +1465,6 @@ export class Frontend {
1661
1465
  config.whiteList.push(data.params.name);
1662
1466
  }
1663
1467
  }
1664
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1665
1468
  if (isValidArray(config.blackList, 1)) {
1666
1469
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1667
1470
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1691,9 +1494,7 @@ export class Frontend {
1691
1494
  return;
1692
1495
  }
1693
1496
  const config = plugin.configJson;
1694
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1695
1497
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1696
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1697
1498
  if (select === 'serial')
1698
1499
  this.log.info(`Unselected device serial ${data.params.serial}`);
1699
1500
  if (select === 'name')
@@ -1702,7 +1503,6 @@ export class Frontend {
1702
1503
  if (config.postfix) {
1703
1504
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1704
1505
  }
1705
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1706
1506
  if (isValidArray(config.whiteList, 1)) {
1707
1507
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1708
1508
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1711,7 +1511,6 @@ export class Frontend {
1711
1511
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1712
1512
  }
1713
1513
  }
1714
- // Add the serial to the blackList
1715
1514
  if (isValidArray(config.blackList)) {
1716
1515
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1717
1516
  config.blackList.push(data.params.serial);
@@ -1744,219 +1543,114 @@ export class Frontend {
1744
1543
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1745
1544
  }
1746
1545
  }
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
1546
  wssSendMessage(level, time, name, message) {
1761
1547
  if (!level || !time || !name || !message)
1762
1548
  return;
1763
- // Remove ANSI escape codes from the message
1764
- // eslint-disable-next-line no-control-regex
1765
1549
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1766
- // Remove leading asterisks from the message
1767
1550
  message = message.replace(/^\*+/, '');
1768
- // Replace all occurrences of \t and \n
1769
1551
  message = message.replace(/[\t\n]/g, '');
1770
- // Remove non-printable characters
1771
- // eslint-disable-next-line no-control-regex
1772
1552
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1773
- // Replace all occurrences of \" with "
1774
1553
  message = message.replace(/\\"/g, '"');
1775
- // Replace all occurrences of angle-brackets with &lt; and &gt;"
1776
1554
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1777
- // Define the maximum allowed length for continuous characters without a space
1778
1555
  const maxContinuousLength = 100;
1779
1556
  const keepStartLength = 20;
1780
1557
  const keepEndLength = 20;
1781
- // Split the message into words
1782
1558
  message = message
1783
1559
  .split(' ')
1784
1560
  .map((word) => {
1785
- // If the word length exceeds the max continuous length, insert spaces and truncate
1786
1561
  if (word.length > maxContinuousLength) {
1787
1562
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1788
1563
  }
1789
1564
  return word;
1790
1565
  })
1791
1566
  .join(' ');
1792
- // Send the message to all connected clients
1793
1567
  this.webSocketServer?.clients.forEach((client) => {
1794
1568
  if (client.readyState === WebSocket.OPEN) {
1795
1569
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1796
1570
  }
1797
1571
  });
1798
1572
  }
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
1573
  wssSendRefreshRequired(changed = null) {
1817
1574
  this.log.debug('Sending a refresh required message to all connected clients');
1818
- // Send the message to all connected clients
1819
1575
  this.webSocketServer?.clients.forEach((client) => {
1820
1576
  if (client.readyState === WebSocket.OPEN) {
1821
1577
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1822
1578
  }
1823
1579
  });
1824
1580
  }
1825
- /**
1826
- * Sends a need to restart WebSocket message to all connected clients.
1827
- *
1828
- */
1829
1581
  wssSendRestartRequired(snackbar = true) {
1830
1582
  this.log.debug('Sending a restart required message to all connected clients');
1831
1583
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1832
1584
  if (snackbar === true)
1833
1585
  this.wssSendSnackbarMessage(`Restart required`, 0);
1834
- // Send the message to all connected clients
1835
1586
  this.webSocketServer?.clients.forEach((client) => {
1836
1587
  if (client.readyState === WebSocket.OPEN) {
1837
1588
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1838
1589
  }
1839
1590
  });
1840
1591
  }
1841
- /**
1842
- * Sends a need to update WebSocket message to all connected clients.
1843
- *
1844
- */
1845
1592
  wssSendUpdateRequired() {
1846
1593
  this.log.debug('Sending an update required message to all connected clients');
1847
1594
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1848
- // Send the message to all connected clients
1849
1595
  this.webSocketServer?.clients.forEach((client) => {
1850
1596
  if (client.readyState === WebSocket.OPEN) {
1851
1597
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1852
1598
  }
1853
1599
  });
1854
1600
  }
1855
- /**
1856
- * Sends a cpu update message to all connected clients.
1857
- *
1858
- */
1859
1601
  wssSendCpuUpdate(cpuUsage) {
1860
1602
  if (hasParameter('debug'))
1861
1603
  this.log.debug('Sending a cpu update message to all connected clients');
1862
- // Send the message to all connected clients
1863
1604
  this.webSocketServer?.clients.forEach((client) => {
1864
1605
  if (client.readyState === WebSocket.OPEN) {
1865
1606
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1866
1607
  }
1867
1608
  });
1868
1609
  }
1869
- /**
1870
- * Sends a memory update message to all connected clients.
1871
- *
1872
- */
1873
1610
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1874
1611
  if (hasParameter('debug'))
1875
1612
  this.log.debug('Sending a memory update message to all connected clients');
1876
- // Send the message to all connected clients
1877
1613
  this.webSocketServer?.clients.forEach((client) => {
1878
1614
  if (client.readyState === WebSocket.OPEN) {
1879
1615
  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
1616
  }
1881
1617
  });
1882
1618
  }
1883
- /**
1884
- * Sends an uptime update message to all connected clients.
1885
- *
1886
- */
1887
1619
  wssSendUptimeUpdate(systemUptime, processUptime) {
1888
1620
  if (hasParameter('debug'))
1889
1621
  this.log.debug('Sending a uptime update message to all connected clients');
1890
- // Send the message to all connected clients
1891
1622
  this.webSocketServer?.clients.forEach((client) => {
1892
1623
  if (client.readyState === WebSocket.OPEN) {
1893
1624
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1894
1625
  }
1895
1626
  });
1896
1627
  }
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
1628
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1905
1629
  this.log.debug('Sending a snackbar message to all connected clients');
1906
- // Send the message to all connected clients
1907
1630
  this.webSocketServer?.clients.forEach((client) => {
1908
1631
  if (client.readyState === WebSocket.OPEN) {
1909
1632
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1910
1633
  }
1911
1634
  });
1912
1635
  }
1913
- /**
1914
- * Sends a close snackbar message to all connected clients.
1915
- * @param {string} message - The message to send.
1916
- *
1917
- */
1918
1636
  wssSendCloseSnackbarMessage(message) {
1919
1637
  this.log.debug('Sending a close snackbar message to all connected clients');
1920
- // Send the message to all connected clients
1921
1638
  this.webSocketServer?.clients.forEach((client) => {
1922
1639
  if (client.readyState === WebSocket.OPEN) {
1923
1640
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1924
1641
  }
1925
1642
  });
1926
1643
  }
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
1644
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1942
1645
  this.log.debug('Sending an attribute update message to all connected clients');
1943
- // Send the message to all connected clients
1944
1646
  this.webSocketServer?.clients.forEach((client) => {
1945
1647
  if (client.readyState === WebSocket.OPEN) {
1946
1648
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1947
1649
  }
1948
1650
  });
1949
1651
  }
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
1652
  wssBroadcastMessage(id, method, params) {
1958
1653
  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
1654
  this.webSocketServer?.clients.forEach((client) => {
1961
1655
  if (client.readyState === WebSocket.OPEN) {
1962
1656
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1964,4 +1658,3 @@ export class Frontend {
1964
1658
  });
1965
1659
  }
1966
1660
  }
1967
- //# sourceMappingURL=frontend.js.map