matterbridge 3.0.1-dev-20250507-a182295 → 3.0.1

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 (146) hide show
  1. package/dist/cli.d.ts +29 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +37 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cluster/export.d.ts +2 -0
  6. package/dist/cluster/export.d.ts.map +1 -0
  7. package/dist/cluster/export.js +2 -0
  8. package/dist/cluster/export.js.map +1 -0
  9. package/dist/defaultConfigSchema.d.ts +27 -0
  10. package/dist/defaultConfigSchema.d.ts.map +1 -0
  11. package/dist/defaultConfigSchema.js +23 -0
  12. package/dist/defaultConfigSchema.js.map +1 -0
  13. package/dist/deviceManager.d.ts +114 -0
  14. package/dist/deviceManager.d.ts.map +1 -0
  15. package/dist/deviceManager.js +94 -1
  16. package/dist/deviceManager.js.map +1 -0
  17. package/dist/frontend.d.ts +240 -0
  18. package/dist/frontend.d.ts.map +1 -0
  19. package/dist/frontend.js +328 -15
  20. package/dist/frontend.js.map +1 -0
  21. package/dist/index.d.ts +35 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +28 -1
  24. package/dist/index.js.map +1 -0
  25. package/dist/logger/export.d.ts +2 -0
  26. package/dist/logger/export.d.ts.map +1 -0
  27. package/dist/logger/export.js +1 -0
  28. package/dist/logger/export.js.map +1 -0
  29. package/dist/matter/behaviors.d.ts +2 -0
  30. package/dist/matter/behaviors.d.ts.map +1 -0
  31. package/dist/matter/behaviors.js +2 -0
  32. package/dist/matter/behaviors.js.map +1 -0
  33. package/dist/matter/clusters.d.ts +2 -0
  34. package/dist/matter/clusters.d.ts.map +1 -0
  35. package/dist/matter/clusters.js +2 -0
  36. package/dist/matter/clusters.js.map +1 -0
  37. package/dist/matter/devices.d.ts +2 -0
  38. package/dist/matter/devices.d.ts.map +1 -0
  39. package/dist/matter/devices.js +2 -0
  40. package/dist/matter/devices.js.map +1 -0
  41. package/dist/matter/endpoints.d.ts +2 -0
  42. package/dist/matter/endpoints.d.ts.map +1 -0
  43. package/dist/matter/endpoints.js +2 -0
  44. package/dist/matter/endpoints.js.map +1 -0
  45. package/dist/matter/export.d.ts +5 -0
  46. package/dist/matter/export.d.ts.map +1 -0
  47. package/dist/matter/export.js +2 -0
  48. package/dist/matter/export.js.map +1 -0
  49. package/dist/matter/types.d.ts +3 -0
  50. package/dist/matter/types.d.ts.map +1 -0
  51. package/dist/matter/types.js +2 -0
  52. package/dist/matter/types.js.map +1 -0
  53. package/dist/matterbridge.d.ts +433 -0
  54. package/dist/matterbridge.d.ts.map +1 -0
  55. package/dist/matterbridge.js +747 -46
  56. package/dist/matterbridge.js.map +1 -0
  57. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  59. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  60. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  61. package/dist/matterbridgeBehaviors.d.ts +1166 -0
  62. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  63. package/dist/matterbridgeBehaviors.js +48 -1
  64. package/dist/matterbridgeBehaviors.js.map +1 -0
  65. package/dist/matterbridgeDeviceTypes.d.ts +494 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  67. package/dist/matterbridgeDeviceTypes.js +431 -12
  68. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  69. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  71. package/dist/matterbridgeDynamicPlatform.js +34 -0
  72. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  73. package/dist/matterbridgeEndpoint.d.ts +956 -0
  74. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  75. package/dist/matterbridgeEndpoint.js +801 -11
  76. package/dist/matterbridgeEndpoint.js.map +1 -0
  77. package/dist/matterbridgeEndpointHelpers.d.ts +2706 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  79. package/dist/matterbridgeEndpointHelpers.js +142 -9
  80. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  81. package/dist/matterbridgePlatform.d.ts +294 -0
  82. package/dist/matterbridgePlatform.d.ts.map +1 -0
  83. package/dist/matterbridgePlatform.js +225 -7
  84. package/dist/matterbridgePlatform.js.map +1 -0
  85. package/dist/matterbridgeTypes.d.ts +187 -0
  86. package/dist/matterbridgeTypes.d.ts.map +1 -0
  87. package/dist/matterbridgeTypes.js +24 -0
  88. package/dist/matterbridgeTypes.js.map +1 -0
  89. package/dist/pluginManager.d.ts +273 -0
  90. package/dist/pluginManager.d.ts.map +1 -0
  91. package/dist/pluginManager.js +264 -3
  92. package/dist/pluginManager.js.map +1 -0
  93. package/dist/shelly.d.ts +92 -0
  94. package/dist/shelly.d.ts.map +1 -0
  95. package/dist/shelly.js +146 -6
  96. package/dist/shelly.js.map +1 -0
  97. package/dist/storage/export.d.ts +2 -0
  98. package/dist/storage/export.d.ts.map +1 -0
  99. package/dist/storage/export.js +1 -0
  100. package/dist/storage/export.js.map +1 -0
  101. package/dist/update.d.ts +32 -0
  102. package/dist/update.d.ts.map +1 -0
  103. package/dist/update.js +52 -0
  104. package/dist/update.js.map +1 -0
  105. package/dist/utils/colorUtils.d.ts +61 -0
  106. package/dist/utils/colorUtils.d.ts.map +1 -0
  107. package/dist/utils/colorUtils.js +205 -2
  108. package/dist/utils/colorUtils.js.map +1 -0
  109. package/dist/utils/copyDirectory.d.ts +32 -0
  110. package/dist/utils/copyDirectory.d.ts.map +1 -0
  111. package/dist/utils/copyDirectory.js +37 -1
  112. package/dist/utils/copyDirectory.js.map +1 -0
  113. package/dist/utils/createZip.d.ts +38 -0
  114. package/dist/utils/createZip.d.ts.map +1 -0
  115. package/dist/utils/createZip.js +42 -2
  116. package/dist/utils/createZip.js.map +1 -0
  117. package/dist/utils/deepCopy.d.ts +31 -0
  118. package/dist/utils/deepCopy.d.ts.map +1 -0
  119. package/dist/utils/deepCopy.js +40 -0
  120. package/dist/utils/deepCopy.js.map +1 -0
  121. package/dist/utils/deepEqual.d.ts +53 -0
  122. package/dist/utils/deepEqual.d.ts.map +1 -0
  123. package/dist/utils/deepEqual.js +65 -1
  124. package/dist/utils/deepEqual.js.map +1 -0
  125. package/dist/utils/export.d.ts +10 -0
  126. package/dist/utils/export.d.ts.map +1 -0
  127. package/dist/utils/export.js +1 -0
  128. package/dist/utils/export.js.map +1 -0
  129. package/dist/utils/isvalid.d.ts +95 -0
  130. package/dist/utils/isvalid.d.ts.map +1 -0
  131. package/dist/utils/isvalid.js +93 -0
  132. package/dist/utils/isvalid.js.map +1 -0
  133. package/dist/utils/network.d.ts +69 -0
  134. package/dist/utils/network.d.ts.map +1 -0
  135. package/dist/utils/network.js +76 -5
  136. package/dist/utils/network.js.map +1 -0
  137. package/dist/utils/parameter.d.ts +58 -0
  138. package/dist/utils/parameter.d.ts.map +1 -0
  139. package/dist/utils/parameter.js +53 -0
  140. package/dist/utils/parameter.js.map +1 -0
  141. package/dist/utils/wait.d.ts +43 -0
  142. package/dist/utils/wait.d.ts.map +1 -0
  143. package/dist/utils/wait.js +48 -5
  144. package/dist/utils/wait.js.map +1 -0
  145. package/npm-shrinkwrap.json +2 -2
  146. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,28 +1,111 @@
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
1
24
  import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
25
+ // Node modules
2
26
  import { createServer } from 'node:http';
3
27
  import https from 'node:https';
4
28
  import os from 'node:os';
5
29
  import path from 'node:path';
6
30
  import { promises as fs } from 'node:fs';
31
+ // Third-party modules
7
32
  import express from 'express';
8
33
  import WebSocket, { WebSocketServer } from 'ws';
9
34
  import multer from 'multer';
35
+ // AnsiLogger module
10
36
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
37
+ // Matterbridge
11
38
  import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/export.js';
12
39
  import { plg } from './matterbridgeTypes.js';
13
40
  import { hasParameter } from './utils/export.js';
14
41
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
42
+ /**
43
+ * Websocket message ID for logging.
44
+ * @constant {number}
45
+ */
15
46
  export const WS_ID_LOG = 0;
47
+ /**
48
+ * Websocket message ID indicating a refresh is needed.
49
+ * @constant {number}
50
+ */
16
51
  export const WS_ID_REFRESH_NEEDED = 1;
52
+ /**
53
+ * Websocket message ID indicating a restart is needed.
54
+ * @constant {number}
55
+ */
17
56
  export const WS_ID_RESTART_NEEDED = 2;
57
+ /**
58
+ * Websocket message ID indicating a cpu update.
59
+ * @constant {number}
60
+ */
18
61
  export const WS_ID_CPU_UPDATE = 3;
62
+ /**
63
+ * Websocket message ID indicating a memory update.
64
+ * @constant {number}
65
+ */
19
66
  export const WS_ID_MEMORY_UPDATE = 4;
67
+ /**
68
+ * Websocket message ID indicating an uptime update.
69
+ * @constant {number}
70
+ */
20
71
  export const WS_ID_UPTIME_UPDATE = 5;
72
+ /**
73
+ * Websocket message ID indicating a snackbar message.
74
+ * @constant {number}
75
+ */
21
76
  export const WS_ID_SNACKBAR = 6;
77
+ /**
78
+ * Websocket message ID indicating matterbridge has un update available.
79
+ * @constant {number}
80
+ */
22
81
  export const WS_ID_UPDATE_NEEDED = 7;
82
+ /**
83
+ * Websocket message ID indicating a state update.
84
+ * @constant {number}
85
+ */
23
86
  export const WS_ID_STATEUPDATE = 8;
87
+ /**
88
+ * Websocket message ID indicating to close a permanent snackbar message.
89
+ * @constant {number}
90
+ */
24
91
  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
+ */
25
100
  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
+ */
26
109
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
27
110
  export class Frontend {
28
111
  matterbridge;
@@ -40,7 +123,7 @@ export class Frontend {
40
123
  memoryTimeout;
41
124
  constructor(matterbridge) {
42
125
  this.matterbridge = matterbridge;
43
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
126
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
44
127
  }
45
128
  set logLevel(logLevel) {
46
129
  this.log.logLevel = logLevel;
@@ -48,13 +131,43 @@ export class Frontend {
48
131
  async start(port = 8283) {
49
132
  this.port = port;
50
133
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
134
+ // Initialize multer with the upload directory
51
135
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
52
136
  await fs.mkdir(uploadDir, { recursive: true });
53
137
  const upload = multer({ dest: uploadDir });
138
+ // Create the express app that serves the frontend
54
139
  this.expressApp = express();
140
+ // Inject logging/debug wrapper for route/middleware registration
141
+ /*
142
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
143
+ for (const method of methods) {
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
146
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
148
+ try {
149
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
150
+ return original(path, ...rest);
151
+ } catch (err) {
152
+ console.error(`[ERROR] Failed to register route: ${path}`);
153
+ throw err;
154
+ }
155
+ };
156
+ }
157
+ */
158
+ // Log all requests to the server for debugging
159
+ /*
160
+ this.expressApp.use((req, res, next) => {
161
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
162
+ next();
163
+ });
164
+ */
165
+ // Serve static files from '/static' endpoint
55
166
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
56
167
  if (!hasParameter('ssl')) {
168
+ // Create an HTTP server and attach the express app
57
169
  this.httpServer = createServer(this.expressApp);
170
+ // Listen on the specified port
58
171
  if (hasParameter('ingress')) {
59
172
  this.httpServer.listen(this.port, '0.0.0.0', () => {
60
173
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -68,6 +181,7 @@ export class Frontend {
68
181
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
69
182
  });
70
183
  }
184
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
185
  this.httpServer.on('error', (error) => {
72
186
  this.log.error(`Frontend http server error listening on ${this.port}`);
73
187
  switch (error.code) {
@@ -83,6 +197,7 @@ export class Frontend {
83
197
  });
84
198
  }
85
199
  else {
200
+ // Load the SSL certificate, the private key and optionally the CA certificate
86
201
  let cert;
87
202
  try {
88
203
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -110,7 +225,9 @@ export class Frontend {
110
225
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
111
226
  }
112
227
  const serverOptions = { cert, key, ca };
228
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
113
229
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
230
+ // Listen on the specified port
114
231
  if (hasParameter('ingress')) {
115
232
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
116
233
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -124,6 +241,7 @@ export class Frontend {
124
241
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
125
242
  });
126
243
  }
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
245
  this.httpsServer.on('error', (error) => {
128
246
  this.log.error(`Frontend https server error listening on ${this.port}`);
129
247
  switch (error.code) {
@@ -140,16 +258,18 @@ export class Frontend {
140
258
  }
141
259
  if (this.initializeError)
142
260
  return;
261
+ // Create a WebSocket server and attach it to the http or https server
143
262
  const wssPort = this.port;
144
263
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
145
264
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
146
265
  this.webSocketServer.on('connection', (ws, request) => {
147
266
  const clientIp = request.socket.remoteAddress;
148
- let callbackLogLevel = "notice";
149
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
150
- callbackLogLevel = "info";
151
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
152
- callbackLogLevel = "debug";
267
+ // Set the global logger callback for the WebSocketServer
268
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
269
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
270
+ callbackLogLevel = "info" /* LogLevel.INFO */;
271
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
272
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
153
273
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
154
274
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
155
275
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -183,6 +303,7 @@ export class Frontend {
183
303
  this.webSocketServer.on('error', (ws, error) => {
184
304
  this.log.error(`WebSocketServer error: ${error}`);
185
305
  });
306
+ // Subscribe to cli events
186
307
  const { cliEmitter } = await import('./cli.js');
187
308
  cliEmitter.removeAllListeners();
188
309
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -194,6 +315,7 @@ export class Frontend {
194
315
  cliEmitter.on('cpu', (cpuUsage) => {
195
316
  this.wssSendCpuUpdate(cpuUsage);
196
317
  });
318
+ // Endpoint to validate login code
197
319
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
198
320
  const { password } = req.body;
199
321
  this.log.debug('The frontend sent /api/login', password);
@@ -212,23 +334,27 @@ export class Frontend {
212
334
  this.log.warn('/api/login error wrong password');
213
335
  res.json({ valid: false });
214
336
  }
337
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
215
338
  }
216
339
  catch (error) {
217
340
  this.log.error('/api/login error getting password');
218
341
  res.json({ valid: false });
219
342
  }
220
343
  });
344
+ // Endpoint to provide health check for docker
221
345
  this.expressApp.get('/health', (req, res) => {
222
346
  this.log.debug('Express received /health');
223
347
  const healthStatus = {
224
- status: 'ok',
225
- uptime: process.uptime(),
226
- timestamp: new Date().toISOString(),
348
+ status: 'ok', // Indicate service is healthy
349
+ uptime: process.uptime(), // Server uptime in seconds
350
+ timestamp: new Date().toISOString(), // Current timestamp
227
351
  };
228
352
  res.status(200).json(healthStatus);
229
353
  });
354
+ // Endpoint to provide memory usage details
230
355
  this.expressApp.get('/memory', async (req, res) => {
231
356
  this.log.debug('Express received /memory');
357
+ // Memory usage from process
232
358
  const memoryUsageRaw = process.memoryUsage();
233
359
  const memoryUsage = {
234
360
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -237,10 +363,13 @@ export class Frontend {
237
363
  external: this.formatMemoryUsage(memoryUsageRaw.external),
238
364
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
239
365
  };
366
+ // V8 heap statistics
240
367
  const { default: v8 } = await import('node:v8');
241
368
  const heapStatsRaw = v8.getHeapStatistics();
242
369
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
370
+ // Format heapStats
243
371
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
372
+ // Format heapSpaces
244
373
  const heapSpaces = heapSpacesRaw.map((space) => ({
245
374
  ...space,
246
375
  space_size: this.formatMemoryUsage(space.space_size),
@@ -258,19 +387,23 @@ export class Frontend {
258
387
  };
259
388
  res.status(200).json(memoryReport);
260
389
  });
390
+ // Endpoint to provide settings
261
391
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
262
392
  this.log.debug('The frontend sent /api/settings');
263
393
  res.json(await this.getApiSettings());
264
394
  });
395
+ // Endpoint to provide plugins
265
396
  this.expressApp.get('/api/plugins', async (req, res) => {
266
397
  this.log.debug('The frontend sent /api/plugins');
267
398
  res.json(this.getBaseRegisteredPlugins());
268
399
  });
400
+ // Endpoint to provide devices
269
401
  this.expressApp.get('/api/devices', async (req, res) => {
270
402
  this.log.debug('The frontend sent /api/devices');
271
403
  const devices = await this.getDevices();
272
404
  res.json(devices);
273
405
  });
406
+ // Endpoint to view the matterbridge log
274
407
  this.expressApp.get('/api/view-mblog', async (req, res) => {
275
408
  this.log.debug('The frontend sent /api/view-mblog');
276
409
  try {
@@ -283,6 +416,7 @@ export class Frontend {
283
416
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
284
417
  }
285
418
  });
419
+ // Endpoint to view the matter.js log
286
420
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
287
421
  this.log.debug('The frontend sent /api/view-mjlog');
288
422
  try {
@@ -295,6 +429,7 @@ export class Frontend {
295
429
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
296
430
  }
297
431
  });
432
+ // Endpoint to view the shelly log
298
433
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
299
434
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
300
435
  try {
@@ -307,6 +442,7 @@ export class Frontend {
307
442
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
308
443
  }
309
444
  });
445
+ // Endpoint to download the matterbridge log
310
446
  this.expressApp.get('/api/download-mblog', async (req, res) => {
311
447
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
312
448
  try {
@@ -319,6 +455,7 @@ export class Frontend {
319
455
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
320
456
  }
321
457
  res.type('text/plain');
458
+ // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
322
459
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
323
460
  if (error) {
324
461
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -326,6 +463,7 @@ export class Frontend {
326
463
  }
327
464
  });
328
465
  });
466
+ // Endpoint to download the matter log
329
467
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
330
468
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
331
469
  try {
@@ -345,6 +483,7 @@ export class Frontend {
345
483
  }
346
484
  });
347
485
  });
486
+ // Endpoint to download the shelly log
348
487
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
349
488
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
350
489
  try {
@@ -364,6 +503,7 @@ export class Frontend {
364
503
  }
365
504
  });
366
505
  });
506
+ // Endpoint to download the matterbridge storage directory
367
507
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
368
508
  this.log.debug('The frontend sent /api/download-mbstorage');
369
509
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -374,6 +514,7 @@ export class Frontend {
374
514
  }
375
515
  });
376
516
  });
517
+ // Endpoint to download the matter storage file
377
518
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
378
519
  this.log.debug('The frontend sent /api/download-mjstorage');
379
520
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -384,6 +525,7 @@ export class Frontend {
384
525
  }
385
526
  });
386
527
  });
528
+ // Endpoint to download the matterbridge plugin directory
387
529
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
388
530
  this.log.debug('The frontend sent /api/download-pluginstorage');
389
531
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -394,9 +536,11 @@ export class Frontend {
394
536
  }
395
537
  });
396
538
  });
539
+ // Endpoint to download the matterbridge plugin config files
397
540
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
398
541
  this.log.debug('The frontend sent /api/download-pluginconfig');
399
542
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
543
+ // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
400
544
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
401
545
  if (error) {
402
546
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -404,6 +548,7 @@ export class Frontend {
404
548
  }
405
549
  });
406
550
  });
551
+ // Endpoint to download the matterbridge plugin config files
407
552
  this.expressApp.get('/api/download-backup', async (req, res) => {
408
553
  this.log.debug('The frontend sent /api/download-backup');
409
554
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -413,6 +558,7 @@ export class Frontend {
413
558
  }
414
559
  });
415
560
  });
561
+ // Endpoint to upload a package
416
562
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
417
563
  const { filename } = req.body;
418
564
  const file = req.file;
@@ -422,10 +568,13 @@ export class Frontend {
422
568
  return;
423
569
  }
424
570
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
571
+ // Define the path where the plugin file will be saved
425
572
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
426
573
  try {
574
+ // Move the uploaded file to the specified path
427
575
  await fs.rename(file.path, filePath);
428
576
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
577
+ // Install the plugin package
429
578
  if (filename.endsWith('.tgz')) {
430
579
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
431
580
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -444,6 +593,7 @@ export class Frontend {
444
593
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
445
594
  }
446
595
  });
596
+ // Fallback for routing (must be the last route)
447
597
  this.expressApp.use((req, res) => {
448
598
  this.log.debug('The frontend sent:', req.url);
449
599
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -451,24 +601,29 @@ export class Frontend {
451
601
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
452
602
  }
453
603
  async stop() {
604
+ // Close the http server
454
605
  if (this.httpServer) {
455
606
  this.httpServer.close();
456
607
  this.httpServer.removeAllListeners();
457
608
  this.httpServer = undefined;
458
609
  this.log.debug('Frontend http server closed successfully');
459
610
  }
611
+ // Close the https server
460
612
  if (this.httpsServer) {
461
613
  this.httpsServer.close();
462
614
  this.httpsServer.removeAllListeners();
463
615
  this.httpsServer = undefined;
464
616
  this.log.debug('Frontend https server closed successfully');
465
617
  }
618
+ // Remove listeners from the express app
466
619
  if (this.expressApp) {
467
620
  this.expressApp.removeAllListeners();
468
621
  this.expressApp = undefined;
469
622
  this.log.debug('Frontend app closed successfully');
470
623
  }
624
+ // Close the WebSocket server
471
625
  if (this.webSocketServer) {
626
+ // Close all active connections
472
627
  this.webSocketServer.clients.forEach((client) => {
473
628
  if (client.readyState === WebSocket.OPEN) {
474
629
  client.close();
@@ -485,6 +640,7 @@ export class Frontend {
485
640
  this.webSocketServer = undefined;
486
641
  }
487
642
  }
643
+ // Function to format bytes to KB, MB, or GB
488
644
  formatMemoryUsage = (bytes) => {
489
645
  if (bytes >= 1024 ** 3) {
490
646
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -496,6 +652,7 @@ export class Frontend {
496
652
  return `${(bytes / 1024).toFixed(2)} KB`;
497
653
  }
498
654
  };
655
+ // Function to format system uptime with only the most significant unit
499
656
  formatOsUpTime = (seconds) => {
500
657
  if (seconds >= 86400) {
501
658
  const days = Math.floor(seconds / 86400);
@@ -511,8 +668,13 @@ export class Frontend {
511
668
  }
512
669
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
513
670
  };
671
+ /**
672
+ * Retrieves the api settings data.
673
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
674
+ */
514
675
  async getApiSettings() {
515
676
  const { lastCpuUsage } = await import('./cli.js');
677
+ // Update the system information
516
678
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
517
679
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
518
680
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -521,6 +683,7 @@ export class Frontend {
521
683
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
522
684
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
523
685
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
686
+ // Update the matterbridge information
524
687
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
525
688
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
526
689
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -539,6 +702,11 @@ export class Frontend {
539
702
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
540
703
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
541
704
  }
705
+ /**
706
+ * Retrieves the reachable attribute.
707
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
708
+ * @returns {boolean} The reachable attribute.
709
+ */
542
710
  getReachability(device) {
543
711
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
544
712
  return false;
@@ -563,13 +731,20 @@ export class Frontend {
563
731
  }
564
732
  return;
565
733
  };
734
+ // Root endpoint
566
735
  if (device.hasClusterServer(PowerSource.Cluster.id))
567
736
  return powerSource(device);
737
+ // Child endpoints
568
738
  for (const child of device.getChildEndpoints()) {
569
739
  if (child.hasClusterServer(PowerSource.Cluster.id))
570
740
  return powerSource(child);
571
741
  }
572
742
  }
743
+ /**
744
+ * Retrieves the cluster text description from a given device.
745
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
746
+ * @returns {string} The attributes description of the cluster servers in the device.
747
+ */
573
748
  getClusterTextFromDevice(device) {
574
749
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
575
750
  return '';
@@ -611,6 +786,7 @@ export class Frontend {
611
786
  let attributes = '';
612
787
  let supportedModes = [];
613
788
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
789
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
614
790
  if (typeof attributeValue === 'undefined')
615
791
  return;
616
792
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -702,8 +878,13 @@ export class Frontend {
702
878
  if (clusterName === 'userLabel' && attributeName === 'labelList')
703
879
  attributes += `${getUserLabel(device)} `;
704
880
  });
881
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
705
882
  return attributes.trimStart().trimEnd();
706
883
  }
884
+ /**
885
+ * Retrieves the base registered plugins sanitized for res.json().
886
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
887
+ */
707
888
  getBaseRegisteredPlugins() {
708
889
  const baseRegisteredPlugins = [];
709
890
  for (const plugin of this.matterbridge.plugins) {
@@ -742,11 +923,18 @@ export class Frontend {
742
923
  }
743
924
  return baseRegisteredPlugins;
744
925
  }
926
+ /**
927
+ * Retrieves the devices from Matterbridge.
928
+ * @param {string} [pluginName] - The name of the plugin to filter devices by.
929
+ * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
930
+ */
745
931
  async getDevices(pluginName) {
746
932
  const devices = [];
747
933
  this.matterbridge.devices.forEach(async (device) => {
934
+ // Filter by pluginName if provided
748
935
  if (pluginName && pluginName !== device.plugin)
749
936
  return;
937
+ // Check if the device has the required properties
750
938
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
751
939
  return;
752
940
  const cluster = this.getClusterTextFromDevice(device);
@@ -766,6 +954,13 @@ export class Frontend {
766
954
  });
767
955
  return devices;
768
956
  }
957
+ /**
958
+ * Handles incoming websocket messages for the Matterbridge frontend.
959
+ *
960
+ * @param {WebSocket} client - The websocket client that sent the message.
961
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
962
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
963
+ */
769
964
  async wsMessageHandler(client, message) {
770
965
  let data;
771
966
  try {
@@ -812,8 +1007,10 @@ export class Frontend {
812
1007
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
813
1008
  const packageName = data.params.packageName.replace(/@.*$/, '');
814
1009
  if (data.params.restart === false && packageName !== 'matterbridge') {
1010
+ // The install comes from InstallPlugins
815
1011
  this.matterbridge.plugins.add(packageName).then((plugin) => {
816
1012
  if (plugin) {
1013
+ // The plugin is not registered
817
1014
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
818
1015
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
819
1016
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
@@ -821,6 +1018,7 @@ export class Frontend {
821
1018
  });
822
1019
  }
823
1020
  else {
1021
+ // The plugin is already registered
824
1022
  this.wssSendSnackbarMessage(`Restart required`, 0);
825
1023
  this.wssSendRefreshRequired('plugins');
826
1024
  this.wssSendRestartRequired();
@@ -828,6 +1026,7 @@ export class Frontend {
828
1026
  });
829
1027
  }
830
1028
  else {
1029
+ // The package is matterbridge
831
1030
  if (this.matterbridge.restartMode !== '') {
832
1031
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
833
1032
  this.matterbridge.shutdownProcess();
@@ -849,6 +1048,7 @@ export class Frontend {
849
1048
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
850
1049
  return;
851
1050
  }
1051
+ // The package is a plugin
852
1052
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
853
1053
  if (plugin) {
854
1054
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -857,6 +1057,7 @@ export class Frontend {
857
1057
  this.wssSendRefreshRequired('plugins');
858
1058
  this.wssSendRefreshRequired('devices');
859
1059
  }
1060
+ // Uninstall the package
860
1061
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
861
1062
  this.matterbridge
862
1063
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1133,6 +1334,7 @@ export class Frontend {
1133
1334
  });
1134
1335
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1135
1336
  deviceTypes = [];
1337
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1136
1338
  const name = childEndpoint.endpoint?.id;
1137
1339
  const clusterServers = childEndpoint.getAllClusterServers();
1138
1340
  clusterServers.forEach((clusterServer) => {
@@ -1246,22 +1448,22 @@ export class Frontend {
1246
1448
  if (isValidString(data.params.value, 4)) {
1247
1449
  this.log.debug('Matterbridge logger level:', data.params.value);
1248
1450
  if (data.params.value === 'Debug') {
1249
- await this.matterbridge.setLogLevel("debug");
1451
+ await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1250
1452
  }
1251
1453
  else if (data.params.value === 'Info') {
1252
- await this.matterbridge.setLogLevel("info");
1454
+ await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1253
1455
  }
1254
1456
  else if (data.params.value === 'Notice') {
1255
- await this.matterbridge.setLogLevel("notice");
1457
+ await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1256
1458
  }
1257
1459
  else if (data.params.value === 'Warn') {
1258
- await this.matterbridge.setLogLevel("warn");
1460
+ await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1259
1461
  }
1260
1462
  else if (data.params.value === 'Error') {
1261
- await this.matterbridge.setLogLevel("error");
1463
+ await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1262
1464
  }
1263
1465
  else if (data.params.value === 'Fatal') {
1264
- await this.matterbridge.setLogLevel("fatal");
1466
+ await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1265
1467
  }
1266
1468
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1267
1469
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1272,6 +1474,7 @@ export class Frontend {
1272
1474
  this.log.debug('Matterbridge file log:', data.params.value);
1273
1475
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1274
1476
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1477
+ // Create the file logger for matterbridge
1275
1478
  if (data.params.value)
1276
1479
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1277
1480
  else
@@ -1427,15 +1630,19 @@ export class Frontend {
1427
1630
  return;
1428
1631
  }
1429
1632
  const config = plugin.configJson;
1633
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1430
1634
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1635
+ // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1431
1636
  if (select === 'serial')
1432
1637
  this.log.info(`Selected device serial ${data.params.serial}`);
1433
1638
  if (select === 'name')
1434
1639
  this.log.info(`Selected device name ${data.params.name}`);
1435
1640
  if (config && select && (select === 'serial' || select === 'name')) {
1641
+ // Remove postfix from the serial if it exists
1436
1642
  if (config.postfix) {
1437
1643
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1438
1644
  }
1645
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1439
1646
  if (isValidArray(config.whiteList, 1)) {
1440
1647
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1441
1648
  config.whiteList.push(data.params.serial);
@@ -1444,6 +1651,7 @@ export class Frontend {
1444
1651
  config.whiteList.push(data.params.name);
1445
1652
  }
1446
1653
  }
1654
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1447
1655
  if (isValidArray(config.blackList, 1)) {
1448
1656
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1449
1657
  config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
@@ -1473,7 +1681,9 @@ export class Frontend {
1473
1681
  return;
1474
1682
  }
1475
1683
  const config = plugin.configJson;
1684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1476
1685
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1686
+ // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1477
1687
  if (select === 'serial')
1478
1688
  this.log.info(`Unselected device serial ${data.params.serial}`);
1479
1689
  if (select === 'name')
@@ -1482,6 +1692,7 @@ export class Frontend {
1482
1692
  if (config.postfix) {
1483
1693
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1484
1694
  }
1695
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1485
1696
  if (isValidArray(config.whiteList, 1)) {
1486
1697
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1487
1698
  config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
@@ -1490,6 +1701,7 @@ export class Frontend {
1490
1701
  config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
1491
1702
  }
1492
1703
  }
1704
+ // Add the serial to the blackList
1493
1705
  if (isValidArray(config.blackList)) {
1494
1706
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1495
1707
  config.blackList.push(data.params.serial);
@@ -1522,114 +1734,214 @@ export class Frontend {
1522
1734
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1523
1735
  }
1524
1736
  }
1737
+ /**
1738
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1739
+ *
1740
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1741
+ * @param {string} time - The time string of the message
1742
+ * @param {string} name - The logger name of the message
1743
+ * @param {string} message - The content of the message.
1744
+ */
1525
1745
  wssSendMessage(level, time, name, message) {
1526
1746
  if (!level || !time || !name || !message)
1527
1747
  return;
1748
+ // Remove ANSI escape codes from the message
1749
+ // eslint-disable-next-line no-control-regex
1528
1750
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1751
+ // Remove leading asterisks from the message
1529
1752
  message = message.replace(/^\*+/, '');
1753
+ // Replace all occurrences of \t and \n
1530
1754
  message = message.replace(/[\t\n]/g, '');
1755
+ // Remove non-printable characters
1756
+ // eslint-disable-next-line no-control-regex
1531
1757
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1758
+ // Replace all occurrences of \" with "
1532
1759
  message = message.replace(/\\"/g, '"');
1760
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1533
1761
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1762
+ // Define the maximum allowed length for continuous characters without a space
1534
1763
  const maxContinuousLength = 100;
1535
1764
  const keepStartLength = 20;
1536
1765
  const keepEndLength = 20;
1766
+ // Split the message into words
1537
1767
  message = message
1538
1768
  .split(' ')
1539
1769
  .map((word) => {
1770
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1540
1771
  if (word.length > maxContinuousLength) {
1541
1772
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1542
1773
  }
1543
1774
  return word;
1544
1775
  })
1545
1776
  .join(' ');
1777
+ // Send the message to all connected clients
1546
1778
  this.webSocketServer?.clients.forEach((client) => {
1547
1779
  if (client.readyState === WebSocket.OPEN) {
1548
1780
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1549
1781
  }
1550
1782
  });
1551
1783
  }
1784
+ /**
1785
+ * Sends a need to refresh WebSocket message to all connected clients.
1786
+ *
1787
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1788
+ * possible values:
1789
+ * - 'matterbridgeLatestVersion'
1790
+ * - 'matterbridgeAdvertise'
1791
+ * - 'online'
1792
+ * - 'offline'
1793
+ * - 'reachability'
1794
+ * - 'settings'
1795
+ * - 'plugins'
1796
+ * - 'pluginsRestart'
1797
+ * - 'devices'
1798
+ * - 'fabrics'
1799
+ * - 'sessions'
1800
+ */
1552
1801
  wssSendRefreshRequired(changed = null) {
1553
1802
  this.log.debug('Sending a refresh required message to all connected clients');
1803
+ // Send the message to all connected clients
1554
1804
  this.webSocketServer?.clients.forEach((client) => {
1555
1805
  if (client.readyState === WebSocket.OPEN) {
1556
1806
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1557
1807
  }
1558
1808
  });
1559
1809
  }
1810
+ /**
1811
+ * Sends a need to restart WebSocket message to all connected clients.
1812
+ *
1813
+ */
1560
1814
  wssSendRestartRequired(snackbar = true) {
1561
1815
  this.log.debug('Sending a restart required message to all connected clients');
1562
1816
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1563
1817
  if (snackbar === true)
1564
1818
  this.wssSendSnackbarMessage(`Restart required`, 0);
1819
+ // Send the message to all connected clients
1565
1820
  this.webSocketServer?.clients.forEach((client) => {
1566
1821
  if (client.readyState === WebSocket.OPEN) {
1567
1822
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1568
1823
  }
1569
1824
  });
1570
1825
  }
1826
+ /**
1827
+ * Sends a need to update WebSocket message to all connected clients.
1828
+ *
1829
+ */
1571
1830
  wssSendUpdateRequired() {
1572
1831
  this.log.debug('Sending an update required message to all connected clients');
1573
1832
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1833
+ // Send the message to all connected clients
1574
1834
  this.webSocketServer?.clients.forEach((client) => {
1575
1835
  if (client.readyState === WebSocket.OPEN) {
1576
1836
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1577
1837
  }
1578
1838
  });
1579
1839
  }
1840
+ /**
1841
+ * Sends a cpu update message to all connected clients.
1842
+ *
1843
+ */
1580
1844
  wssSendCpuUpdate(cpuUsage) {
1581
1845
  if (hasParameter('debug'))
1582
1846
  this.log.debug('Sending a cpu update message to all connected clients');
1847
+ // Send the message to all connected clients
1583
1848
  this.webSocketServer?.clients.forEach((client) => {
1584
1849
  if (client.readyState === WebSocket.OPEN) {
1585
1850
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1586
1851
  }
1587
1852
  });
1588
1853
  }
1854
+ /**
1855
+ * Sends a memory update message to all connected clients.
1856
+ *
1857
+ */
1589
1858
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1590
1859
  if (hasParameter('debug'))
1591
1860
  this.log.debug('Sending a memory update message to all connected clients');
1861
+ // Send the message to all connected clients
1592
1862
  this.webSocketServer?.clients.forEach((client) => {
1593
1863
  if (client.readyState === WebSocket.OPEN) {
1594
1864
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1595
1865
  }
1596
1866
  });
1597
1867
  }
1868
+ /**
1869
+ * Sends an uptime update message to all connected clients.
1870
+ *
1871
+ */
1598
1872
  wssSendUptimeUpdate(systemUptime, processUptime) {
1599
1873
  if (hasParameter('debug'))
1600
1874
  this.log.debug('Sending a uptime update message to all connected clients');
1875
+ // Send the message to all connected clients
1601
1876
  this.webSocketServer?.clients.forEach((client) => {
1602
1877
  if (client.readyState === WebSocket.OPEN) {
1603
1878
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1604
1879
  }
1605
1880
  });
1606
1881
  }
1882
+ /**
1883
+ * Sends an open snackbar message to all connected clients.
1884
+ * @param {string} message - The message to send.
1885
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1886
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1887
+ *
1888
+ */
1607
1889
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1608
1890
  this.log.debug('Sending a snackbar message to all connected clients');
1891
+ // Send the message to all connected clients
1609
1892
  this.webSocketServer?.clients.forEach((client) => {
1610
1893
  if (client.readyState === WebSocket.OPEN) {
1611
1894
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1612
1895
  }
1613
1896
  });
1614
1897
  }
1898
+ /**
1899
+ * Sends a close snackbar message to all connected clients.
1900
+ * @param {string} message - The message to send.
1901
+ *
1902
+ */
1615
1903
  wssSendCloseSnackbarMessage(message) {
1616
1904
  this.log.debug('Sending a close snackbar message to all connected clients');
1905
+ // Send the message to all connected clients
1617
1906
  this.webSocketServer?.clients.forEach((client) => {
1618
1907
  if (client.readyState === WebSocket.OPEN) {
1619
1908
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1620
1909
  }
1621
1910
  });
1622
1911
  }
1912
+ /**
1913
+ * Sends an attribute update message to all connected WebSocket clients.
1914
+ *
1915
+ * @param {string | undefined} plugin - The name of the plugin.
1916
+ * @param {string | undefined} serialNumber - The serial number of the device.
1917
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
1918
+ * @param {string} cluster - The cluster name where the attribute belongs.
1919
+ * @param {string} attribute - The name of the attribute that changed.
1920
+ * @param {number | string | boolean} value - The new value of the attribute.
1921
+ *
1922
+ * @remarks
1923
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1924
+ * with the updated attribute information.
1925
+ */
1623
1926
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1624
1927
  this.log.debug('Sending an attribute update message to all connected clients');
1928
+ // Send the message to all connected clients
1625
1929
  this.webSocketServer?.clients.forEach((client) => {
1626
1930
  if (client.readyState === WebSocket.OPEN) {
1627
1931
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1628
1932
  }
1629
1933
  });
1630
1934
  }
1935
+ /**
1936
+ * Sends a message to all connected clients.
1937
+ * @param {number} id - The message id.
1938
+ * @param {string} method - The message method.
1939
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1940
+ *
1941
+ */
1631
1942
  wssBroadcastMessage(id, method, params) {
1632
1943
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1944
+ // Send the message to all connected clients
1633
1945
  this.webSocketServer?.clients.forEach((client) => {
1634
1946
  if (client.readyState === WebSocket.OPEN) {
1635
1947
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1637,3 +1949,4 @@ export class Frontend {
1637
1949
  });
1638
1950
  }
1639
1951
  }
1952
+ //# sourceMappingURL=frontend.js.map