matterbridge 2.2.2-dev.3 → 2.2.3

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 (152) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +37 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +114 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +94 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/frontend.d.ts +201 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +307 -24
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -1
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +5 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +412 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +723 -49
  57. package/dist/matterbridge.js.map +1 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  60. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  61. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  62. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  63. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  64. package/dist/matterbridgeBehaviors.js +32 -1
  65. package/dist/matterbridgeBehaviors.js.map +1 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeDeviceTypes.js +112 -11
  69. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  72. package/dist/matterbridgeDynamicPlatform.js +33 -0
  73. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  74. package/dist/matterbridgeEndpoint.d.ts +835 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +690 -6
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +118 -9
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +251 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +185 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +178 -0
  87. package/dist/matterbridgeTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeTypes.js +24 -0
  89. package/dist/matterbridgeTypes.js.map +1 -0
  90. package/dist/pluginManager.d.ts +236 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +229 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/shelly.d.ts +77 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +121 -6
  97. package/dist/shelly.js.map +1 -0
  98. package/dist/storage/export.d.ts +2 -0
  99. package/dist/storage/export.d.ts.map +1 -0
  100. package/dist/storage/export.js +1 -0
  101. package/dist/storage/export.js.map +1 -0
  102. package/dist/update.d.ts +32 -0
  103. package/dist/update.d.ts.map +1 -0
  104. package/dist/update.js +45 -0
  105. package/dist/update.js.map +1 -0
  106. package/dist/utils/colorUtils.d.ts +61 -0
  107. package/dist/utils/colorUtils.d.ts.map +1 -0
  108. package/dist/utils/colorUtils.js +205 -2
  109. package/dist/utils/colorUtils.js.map +1 -0
  110. package/dist/utils/copyDirectory.d.ts +32 -0
  111. package/dist/utils/copyDirectory.d.ts.map +1 -0
  112. package/dist/utils/copyDirectory.js +37 -1
  113. package/dist/utils/copyDirectory.js.map +1 -0
  114. package/dist/utils/createZip.d.ts +38 -0
  115. package/dist/utils/createZip.d.ts.map +1 -0
  116. package/dist/utils/createZip.js +42 -2
  117. package/dist/utils/createZip.js.map +1 -0
  118. package/dist/utils/deepCopy.d.ts +31 -0
  119. package/dist/utils/deepCopy.d.ts.map +1 -0
  120. package/dist/utils/deepCopy.js +40 -0
  121. package/dist/utils/deepCopy.js.map +1 -0
  122. package/dist/utils/deepEqual.d.ts +53 -0
  123. package/dist/utils/deepEqual.d.ts.map +1 -0
  124. package/dist/utils/deepEqual.js +65 -1
  125. package/dist/utils/deepEqual.js.map +1 -0
  126. package/dist/utils/export.d.ts +10 -0
  127. package/dist/utils/export.d.ts.map +1 -0
  128. package/dist/utils/export.js +1 -0
  129. package/dist/utils/export.js.map +1 -0
  130. package/dist/utils/isvalid.d.ts +87 -0
  131. package/dist/utils/isvalid.d.ts.map +1 -0
  132. package/dist/utils/isvalid.js +86 -0
  133. package/dist/utils/isvalid.js.map +1 -0
  134. package/dist/utils/network.d.ts +70 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +77 -5
  137. package/dist/utils/network.js.map +1 -0
  138. package/dist/utils/parameter.d.ts +44 -0
  139. package/dist/utils/parameter.d.ts.map +1 -0
  140. package/dist/utils/parameter.js +41 -0
  141. package/dist/utils/parameter.js.map +1 -0
  142. package/dist/utils/wait.d.ts +43 -0
  143. package/dist/utils/wait.d.ts.map +1 -0
  144. package/dist/utils/wait.js +48 -5
  145. package/dist/utils/wait.js.map +1 -0
  146. package/frontend/build/asset-manifest.json +3 -3
  147. package/frontend/build/index.html +1 -1
  148. package/frontend/build/static/js/{main.a02de6aa.js → main.6ffd2c31.js} +6 -6
  149. package/frontend/build/static/js/{main.a02de6aa.js.map → main.6ffd2c31.js.map} +1 -1
  150. package/npm-shrinkwrap.json +2 -2
  151. package/package.json +2 -1
  152. /package/frontend/build/static/js/{main.a02de6aa.js.LICENSE.txt → main.6ffd2c31.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,4 +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
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 os from 'node:os';
4
28
  import path from 'node:path';
@@ -7,21 +31,70 @@ import https from 'https';
7
31
  import express from 'express';
8
32
  import WebSocket, { WebSocketServer } from 'ws';
9
33
  import multer from 'multer';
34
+ // AnsiLogger module
10
35
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
36
+ // Matterbridge
11
37
  import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString } from './utils/export.js';
12
38
  import { plg } from './matterbridgeTypes.js';
13
39
  import { hasParameter } from './utils/export.js';
14
40
  import { BridgedDeviceBasicInformation } from '@matter/main/clusters';
15
- import { cliEmitter } from './cli.js';
41
+ /**
42
+ * Websocket message ID for logging.
43
+ * @constant {number}
44
+ */
16
45
  export const WS_ID_LOG = 0;
46
+ /**
47
+ * Websocket message ID indicating a refresh is needed.
48
+ * @constant {number}
49
+ */
17
50
  export const WS_ID_REFRESH_NEEDED = 1;
51
+ /**
52
+ * Websocket message ID indicating a restart is needed.
53
+ * @constant {number}
54
+ */
18
55
  export const WS_ID_RESTART_NEEDED = 2;
56
+ /**
57
+ * Websocket message ID indicating a cpu update.
58
+ * @constant {number}
59
+ */
19
60
  export const WS_ID_CPU_UPDATE = 3;
61
+ /**
62
+ * Websocket message ID indicating a memory update.
63
+ * @constant {number}
64
+ */
20
65
  export const WS_ID_MEMORY_UPDATE = 4;
66
+ /**
67
+ * Websocket message ID indicating an uptime update.
68
+ * @constant {number}
69
+ */
21
70
  export const WS_ID_UPTIME_UPDATE = 5;
71
+ /**
72
+ * Websocket message ID indicating a memory update.
73
+ * @constant {number}
74
+ */
22
75
  export const WS_ID_SNACKBAR = 6;
76
+ /**
77
+ * Websocket message ID indicating a memory update.
78
+ * @constant {number}
79
+ */
23
80
  export const WS_ID_UPDATE_NEEDED = 7;
81
+ /**
82
+ * Websocket message ID indicating a shelly system update.
83
+ * check:
84
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
85
+ * perform:
86
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
87
+ * @constant {number}
88
+ */
24
89
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
90
+ /**
91
+ * Websocket message ID indicating a shelly main update.
92
+ * check:
93
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
94
+ * perform:
95
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
96
+ * @constant {number}
97
+ */
25
98
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
26
99
  export class Frontend {
27
100
  matterbridge;
@@ -39,7 +112,7 @@ export class Frontend {
39
112
  memoryTimeout;
40
113
  constructor(matterbridge) {
41
114
  this.matterbridge = matterbridge;
42
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
115
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
43
116
  }
44
117
  set logLevel(logLevel) {
45
118
  this.log.logLevel = logLevel;
@@ -47,13 +120,25 @@ export class Frontend {
47
120
  async start(port = 8283) {
48
121
  this.port = port;
49
122
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
123
+ // Initialize multer with the upload directory
50
124
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
51
125
  await fs.mkdir(uploadDir, { recursive: true });
52
126
  const upload = multer({ dest: uploadDir });
127
+ // Create the express app that serves the frontend
53
128
  this.expressApp = express();
129
+ // Log all requests to the server for debugging
130
+ /*
131
+ this.expressApp.use((req, res, next) => {
132
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
133
+ next();
134
+ });
135
+ */
136
+ // Serve static files from '/static' endpoint
54
137
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
55
138
  if (!hasParameter('ssl')) {
139
+ // Create an HTTP server and attach the express app
56
140
  this.httpServer = createServer(this.expressApp);
141
+ // Listen on the specified port
57
142
  if (hasParameter('ingress')) {
58
143
  this.httpServer.listen(this.port, '0.0.0.0', () => {
59
144
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -67,6 +152,7 @@ export class Frontend {
67
152
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
68
153
  });
69
154
  }
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
156
  this.httpServer.on('error', (error) => {
71
157
  this.log.error(`Frontend http server error listening on ${this.port}`);
72
158
  switch (error.code) {
@@ -82,6 +168,7 @@ export class Frontend {
82
168
  });
83
169
  }
84
170
  else {
171
+ // Load the SSL certificate, the private key and optionally the CA certificate
85
172
  let cert;
86
173
  try {
87
174
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -109,7 +196,9 @@ export class Frontend {
109
196
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
110
197
  }
111
198
  const serverOptions = { cert, key, ca };
199
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
112
200
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
201
+ // Listen on the specified port
113
202
  if (hasParameter('ingress')) {
114
203
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
115
204
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -123,6 +212,7 @@ export class Frontend {
123
212
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
124
213
  });
125
214
  }
215
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
216
  this.httpsServer.on('error', (error) => {
127
217
  this.log.error(`Frontend https server error listening on ${this.port}`);
128
218
  switch (error.code) {
@@ -139,16 +229,18 @@ export class Frontend {
139
229
  }
140
230
  if (this.initializeError)
141
231
  return;
232
+ // Create a WebSocket server and attach it to the http or https server
142
233
  const wssPort = this.port;
143
234
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
144
235
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
145
236
  this.webSocketServer.on('connection', (ws, request) => {
146
237
  const clientIp = request.socket.remoteAddress;
147
- let callbackLogLevel = "notice";
148
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
149
- callbackLogLevel = "info";
150
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
151
- callbackLogLevel = "debug";
238
+ // Set the global logger callback for the WebSocketServer
239
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
240
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
241
+ callbackLogLevel = "info" /* LogLevel.INFO */;
242
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
243
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
152
244
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
153
245
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
154
246
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -182,6 +274,8 @@ export class Frontend {
182
274
  this.webSocketServer.on('error', (ws, error) => {
183
275
  this.log.error(`WebSocketServer error: ${error}`);
184
276
  });
277
+ // Subscribe to cli events
278
+ const { cliEmitter } = await import('./cli.js');
185
279
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
186
280
  this.wssSendUptimeUpdate(systemUptime, processUptime);
187
281
  });
@@ -191,6 +285,7 @@ export class Frontend {
191
285
  cliEmitter.on('cpu', (cpuUsage) => {
192
286
  this.wssSendCpuUpdate(cpuUsage);
193
287
  });
288
+ // Endpoint to validate login code
194
289
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
195
290
  const { password } = req.body;
196
291
  this.log.debug('The frontend sent /api/login', password);
@@ -209,23 +304,27 @@ export class Frontend {
209
304
  this.log.warn('/api/login error wrong password');
210
305
  res.json({ valid: false });
211
306
  }
307
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
212
308
  }
213
309
  catch (error) {
214
310
  this.log.error('/api/login error getting password');
215
311
  res.json({ valid: false });
216
312
  }
217
313
  });
314
+ // Endpoint to provide health check
218
315
  this.expressApp.get('/health', (req, res) => {
219
316
  this.log.debug('Express received /health');
220
317
  const healthStatus = {
221
- status: 'ok',
222
- uptime: process.uptime(),
223
- timestamp: new Date().toISOString(),
318
+ status: 'ok', // Indicate service is healthy
319
+ uptime: process.uptime(), // Server uptime in seconds
320
+ timestamp: new Date().toISOString(), // Current timestamp
224
321
  };
225
322
  res.status(200).json(healthStatus);
226
323
  });
324
+ // Endpoint to provide memory usage details
227
325
  this.expressApp.get('/memory', async (req, res) => {
228
326
  this.log.debug('Express received /memory');
327
+ // Memory usage from process
229
328
  const memoryUsageRaw = process.memoryUsage();
230
329
  const memoryUsage = {
231
330
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -234,10 +333,13 @@ export class Frontend {
234
333
  external: this.formatMemoryUsage(memoryUsageRaw.external),
235
334
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
236
335
  };
336
+ // V8 heap statistics
237
337
  const { default: v8 } = await import('node:v8');
238
338
  const heapStatsRaw = v8.getHeapStatistics();
239
339
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
340
+ // Format heapStats
240
341
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
342
+ // Format heapSpaces
241
343
  const heapSpaces = heapSpacesRaw.map((space) => ({
242
344
  ...space,
243
345
  space_size: this.formatMemoryUsage(space.space_size),
@@ -255,6 +357,7 @@ export class Frontend {
255
357
  };
256
358
  res.status(200).json(memoryReport);
257
359
  });
360
+ // Endpoint to start advertising the server node
258
361
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
259
362
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
260
363
  if (pairingCodes) {
@@ -265,18 +368,22 @@ export class Frontend {
265
368
  res.status(500).json({ error: 'Failed to generate pairing codes' });
266
369
  }
267
370
  });
371
+ // Endpoint to provide settings
268
372
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
269
373
  this.log.debug('The frontend sent /api/settings');
270
374
  res.json(await this.getApiSettings());
271
375
  });
376
+ // Endpoint to provide plugins
272
377
  this.expressApp.get('/api/plugins', async (req, res) => {
273
378
  this.log.debug('The frontend sent /api/plugins');
274
379
  res.json(this.getBaseRegisteredPlugins());
275
380
  });
381
+ // Endpoint to provide devices
276
382
  this.expressApp.get('/api/devices', (req, res) => {
277
383
  this.log.debug('The frontend sent /api/devices');
278
384
  const devices = [];
279
385
  this.matterbridge.devices.forEach(async (device) => {
386
+ // Check if the device has the required properties
280
387
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
281
388
  return;
282
389
  const cluster = this.getClusterTextFromDevice(device);
@@ -295,6 +402,7 @@ export class Frontend {
295
402
  });
296
403
  res.json(devices);
297
404
  });
405
+ // Endpoint to provide the cluster servers of the devices
298
406
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
299
407
  const selectedPluginName = req.params.selectedPluginName;
300
408
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -367,6 +475,7 @@ export class Frontend {
367
475
  });
368
476
  res.json(data);
369
477
  });
478
+ // Endpoint to view the log
370
479
  this.expressApp.get('/api/view-log', async (req, res) => {
371
480
  this.log.debug('The frontend sent /api/log');
372
481
  try {
@@ -379,10 +488,12 @@ export class Frontend {
379
488
  res.status(500).send('Error reading log file');
380
489
  }
381
490
  });
491
+ // Endpoint to download the matterbridge log
382
492
  this.expressApp.get('/api/download-mblog', async (req, res) => {
383
493
  this.log.debug('The frontend sent /api/download-mblog');
384
494
  try {
385
495
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
496
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
386
497
  }
387
498
  catch (error) {
388
499
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -394,10 +505,12 @@ export class Frontend {
394
505
  }
395
506
  });
396
507
  });
508
+ // Endpoint to download the matter log
397
509
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
398
510
  this.log.debug('The frontend sent /api/download-mjlog');
399
511
  try {
400
512
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
513
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
401
514
  }
402
515
  catch (error) {
403
516
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -409,10 +522,12 @@ export class Frontend {
409
522
  }
410
523
  });
411
524
  });
525
+ // Endpoint to download the matter log
412
526
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
413
527
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
414
528
  try {
415
529
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
530
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
416
531
  }
417
532
  catch (error) {
418
533
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
@@ -424,6 +539,7 @@ export class Frontend {
424
539
  }
425
540
  });
426
541
  });
542
+ // Endpoint to download the matter storage file
427
543
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
428
544
  this.log.debug('The frontend sent /api/download-mjstorage');
429
545
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -434,6 +550,7 @@ export class Frontend {
434
550
  }
435
551
  });
436
552
  });
553
+ // Endpoint to download the matterbridge storage directory
437
554
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
438
555
  this.log.debug('The frontend sent /api/download-mbstorage');
439
556
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -444,6 +561,7 @@ export class Frontend {
444
561
  }
445
562
  });
446
563
  });
564
+ // Endpoint to download the matterbridge plugin directory
447
565
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
448
566
  this.log.debug('The frontend sent /api/download-pluginstorage');
449
567
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -454,9 +572,11 @@ export class Frontend {
454
572
  }
455
573
  });
456
574
  });
575
+ // Endpoint to download the matterbridge plugin config files
457
576
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
458
577
  this.log.debug('The frontend sent /api/download-pluginconfig');
459
578
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
579
+ // 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')));
460
580
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
461
581
  if (error) {
462
582
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -464,6 +584,7 @@ export class Frontend {
464
584
  }
465
585
  });
466
586
  });
587
+ // Endpoint to download the matterbridge plugin config files
467
588
  this.expressApp.get('/api/download-backup', async (req, res) => {
468
589
  this.log.debug('The frontend sent /api/download-backup');
469
590
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -473,6 +594,7 @@ export class Frontend {
473
594
  }
474
595
  });
475
596
  });
597
+ // Endpoint to receive commands
476
598
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
477
599
  const command = req.params.command;
478
600
  let param = req.params.param;
@@ -482,13 +604,15 @@ export class Frontend {
482
604
  return;
483
605
  }
484
606
  this.log.debug(`Received frontend command: ${command}:${param}`);
607
+ // Handle the command setpassword from Settings
485
608
  if (command === 'setpassword') {
486
- const password = param.slice(1, -1);
609
+ const password = param.slice(1, -1); // Remove the first and last characters
487
610
  this.log.debug('setpassword', param, password);
488
611
  await this.matterbridge.nodeContext?.set('password', password);
489
612
  res.json({ message: 'Command received' });
490
613
  return;
491
614
  }
615
+ // Handle the command setbridgemode from Settings
492
616
  if (command === 'setbridgemode') {
493
617
  this.log.debug(`setbridgemode: ${param}`);
494
618
  this.wssSendRestartRequired();
@@ -496,6 +620,7 @@ export class Frontend {
496
620
  res.json({ message: 'Command received' });
497
621
  return;
498
622
  }
623
+ // Handle the command backup from Settings
499
624
  if (command === 'backup') {
500
625
  this.log.notice(`Prepairing the backup...`);
501
626
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -504,31 +629,33 @@ export class Frontend {
504
629
  res.json({ message: 'Command received' });
505
630
  return;
506
631
  }
632
+ // Handle the command setmbloglevel from Settings
507
633
  if (command === 'setmbloglevel') {
508
634
  this.log.debug('Matterbridge log level:', param);
509
635
  if (param === 'Debug') {
510
- this.log.logLevel = "debug";
636
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
511
637
  }
512
638
  else if (param === 'Info') {
513
- this.log.logLevel = "info";
639
+ this.log.logLevel = "info" /* LogLevel.INFO */;
514
640
  }
515
641
  else if (param === 'Notice') {
516
- this.log.logLevel = "notice";
642
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
517
643
  }
518
644
  else if (param === 'Warn') {
519
- this.log.logLevel = "warn";
645
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
520
646
  }
521
647
  else if (param === 'Error') {
522
- this.log.logLevel = "error";
648
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
523
649
  }
524
650
  else if (param === 'Fatal') {
525
- this.log.logLevel = "fatal";
651
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
526
652
  }
527
653
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
528
654
  await this.matterbridge.setLogLevel(this.log.logLevel);
529
655
  res.json({ message: 'Command received' });
530
656
  return;
531
657
  }
658
+ // Handle the command setmbloglevel from Settings
532
659
  if (command === 'setmjloglevel') {
533
660
  this.log.debug('Matter.js log level:', param);
534
661
  if (param === 'Debug') {
@@ -553,30 +680,34 @@ export class Frontend {
553
680
  res.json({ message: 'Command received' });
554
681
  return;
555
682
  }
683
+ // Handle the command setmdnsinterface from Settings
556
684
  if (command === 'setmdnsinterface') {
557
- param = param.slice(1, -1);
685
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
558
686
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
559
687
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
560
688
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
561
689
  res.json({ message: 'Command received' });
562
690
  return;
563
691
  }
692
+ // Handle the command setipv4address from Settings
564
693
  if (command === 'setipv4address') {
565
- param = param.slice(1, -1);
694
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
566
695
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
567
696
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
568
697
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
569
698
  res.json({ message: 'Command received' });
570
699
  return;
571
700
  }
701
+ // Handle the command setipv6address from Settings
572
702
  if (command === 'setipv6address') {
573
- param = param.slice(1, -1);
703
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
574
704
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
575
705
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
576
706
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
577
707
  res.json({ message: 'Command received' });
578
708
  return;
579
709
  }
710
+ // Handle the command setmatterport from Settings
580
711
  if (command === 'setmatterport') {
581
712
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
582
713
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -585,6 +716,7 @@ export class Frontend {
585
716
  res.json({ message: 'Command received' });
586
717
  return;
587
718
  }
719
+ // Handle the command setmatterdiscriminator from Settings
588
720
  if (command === 'setmatterdiscriminator') {
589
721
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
590
722
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -593,6 +725,7 @@ export class Frontend {
593
725
  res.json({ message: 'Command received' });
594
726
  return;
595
727
  }
728
+ // Handle the command setmatterpasscode from Settings
596
729
  if (command === 'setmatterpasscode') {
597
730
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
598
731
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -601,17 +734,20 @@ export class Frontend {
601
734
  res.json({ message: 'Command received' });
602
735
  return;
603
736
  }
737
+ // Handle the command setmbloglevel from Settings
604
738
  if (command === 'setmblogfile') {
605
739
  this.log.debug('Matterbridge file log:', param);
606
740
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
607
741
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
742
+ // Create the file logger for matterbridge
608
743
  if (param === 'true')
609
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
744
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
610
745
  else
611
746
  AnsiLogger.setGlobalLogfile(undefined);
612
747
  res.json({ message: 'Command received' });
613
748
  return;
614
749
  }
750
+ // Handle the command setmbloglevel from Settings
615
751
  if (command === 'setmjlogfile') {
616
752
  this.log.debug('Matter file log:', param);
617
753
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -638,40 +774,48 @@ export class Frontend {
638
774
  res.json({ message: 'Command received' });
639
775
  return;
640
776
  }
777
+ // Handle the command unregister from Settings
641
778
  if (command === 'unregister') {
642
779
  await this.matterbridge.unregisterAndShutdownProcess();
643
780
  res.json({ message: 'Command received' });
644
781
  return;
645
782
  }
783
+ // Handle the command reset from Settings
646
784
  if (command === 'reset') {
647
785
  await this.matterbridge.shutdownProcessAndReset();
648
786
  res.json({ message: 'Command received' });
649
787
  return;
650
788
  }
789
+ // Handle the command factoryreset from Settings
651
790
  if (command === 'factoryreset') {
652
791
  await this.matterbridge.shutdownProcessAndFactoryReset();
653
792
  res.json({ message: 'Command received' });
654
793
  return;
655
794
  }
795
+ // Handle the command shutdown from Header
656
796
  if (command === 'shutdown') {
657
797
  await this.matterbridge.shutdownProcess();
658
798
  res.json({ message: 'Command received' });
659
799
  return;
660
800
  }
801
+ // Handle the command restart from Header
661
802
  if (command === 'restart') {
662
803
  await this.matterbridge.restartProcess();
663
804
  res.json({ message: 'Command received' });
664
805
  return;
665
806
  }
807
+ // Handle the command update from Header
666
808
  if (command === 'update') {
667
809
  await this.matterbridge.updateProcess();
668
810
  this.wssSendRestartRequired();
669
811
  res.json({ message: 'Command received' });
670
812
  return;
671
813
  }
814
+ // Handle the command saveconfig from Home
672
815
  if (command === 'saveconfig') {
673
816
  param = param.replace(/\*/g, '\\');
674
817
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
818
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
675
819
  if (!this.matterbridge.plugins.has(param)) {
676
820
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
677
821
  }
@@ -686,6 +830,7 @@ export class Frontend {
686
830
  res.json({ message: 'Command received' });
687
831
  return;
688
832
  }
833
+ // Handle the command installplugin from Home
689
834
  if (command === 'installplugin') {
690
835
  param = param.replace(/\*/g, '\\');
691
836
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
@@ -694,6 +839,7 @@ export class Frontend {
694
839
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
695
840
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
696
841
  this.wssSendSnackbarMessage(`Installed package ${param}`);
842
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
697
843
  }
698
844
  catch (error) {
699
845
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
@@ -701,17 +847,22 @@ export class Frontend {
701
847
  }
702
848
  this.wssSendRestartRequired();
703
849
  param = param.split('@')[0];
850
+ // Also add the plugin to matterbridge so no return!
704
851
  if (param === 'matterbridge') {
852
+ // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
705
853
  res.json({ message: 'Command received' });
706
854
  return;
707
855
  }
708
856
  }
857
+ // Handle the command addplugin from Home
709
858
  if (command === 'addplugin' || command === 'installplugin') {
710
859
  param = param.replace(/\*/g, '\\');
711
860
  const plugin = await this.matterbridge.plugins.add(param);
712
861
  if (plugin) {
713
862
  this.wssSendSnackbarMessage(`Added plugin ${param}`);
714
863
  if (this.matterbridge.bridgeMode === 'childbridge') {
864
+ // We don't know now if the plugin is a dynamic platform or an accessory platform so we create the server node and the aggregator node
865
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
715
866
  this.matterbridge.createDynamicPlugin(plugin, true);
716
867
  }
717
868
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
@@ -721,13 +872,14 @@ export class Frontend {
721
872
  res.json({ message: 'Command received' });
722
873
  return;
723
874
  }
875
+ // Handle the command removeplugin from Home
724
876
  if (command === 'removeplugin') {
725
877
  if (!this.matterbridge.plugins.has(param)) {
726
878
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
727
879
  }
728
880
  else {
729
881
  const plugin = this.matterbridge.plugins.get(param);
730
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
882
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
731
883
  await this.matterbridge.plugins.remove(param);
732
884
  this.wssSendSnackbarMessage(`Removed plugin ${param}`);
733
885
  this.wssSendRefreshRequired('plugins');
@@ -735,6 +887,7 @@ export class Frontend {
735
887
  res.json({ message: 'Command received' });
736
888
  return;
737
889
  }
890
+ // Handle the command enableplugin from Home
738
891
  if (command === 'enableplugin') {
739
892
  if (!this.matterbridge.plugins.has(param)) {
740
893
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -753,6 +906,7 @@ export class Frontend {
753
906
  await this.matterbridge.plugins.enable(param);
754
907
  this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
755
908
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
909
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
756
910
  this.matterbridge.createDynamicPlugin(plugin, true);
757
911
  }
758
912
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
@@ -763,6 +917,7 @@ export class Frontend {
763
917
  res.json({ message: 'Command received' });
764
918
  return;
765
919
  }
920
+ // Handle the command disableplugin from Home
766
921
  if (command === 'disableplugin') {
767
922
  if (!this.matterbridge.plugins.has(param)) {
768
923
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -770,7 +925,7 @@ export class Frontend {
770
925
  else {
771
926
  const plugin = this.matterbridge.plugins.get(param);
772
927
  if (plugin && plugin.enabled) {
773
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
928
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
774
929
  await this.matterbridge.plugins.disable(param);
775
930
  this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
776
931
  this.wssSendRefreshRequired('plugins');
@@ -788,10 +943,13 @@ export class Frontend {
788
943
  res.status(400).send('Invalid request: file and filename are required');
789
944
  return;
790
945
  }
946
+ // Define the path where the plugin file will be saved
791
947
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
792
948
  try {
949
+ // Move the uploaded file to the specified path
793
950
  await fs.rename(file.path, filePath);
794
951
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
952
+ // Install the plugin package
795
953
  if (filename.endsWith('.tgz')) {
796
954
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
797
955
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -807,6 +965,7 @@ export class Frontend {
807
965
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
808
966
  }
809
967
  });
968
+ // Fallback for routing (must be the last route)
810
969
  this.expressApp.get('*', (req, res) => {
811
970
  this.log.debug('The frontend sent:', req.url);
812
971
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -815,25 +974,31 @@ export class Frontend {
815
974
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
816
975
  }
817
976
  async stop() {
818
- cliEmitter.removeAllListeners();
977
+ // Remove all listeners from the cliEmitter
978
+ // cliEmitter.removeAllListeners();
979
+ // Close the http server
819
980
  if (this.httpServer) {
820
981
  this.httpServer.close();
821
982
  this.httpServer.removeAllListeners();
822
983
  this.httpServer = undefined;
823
984
  this.log.debug('Frontend http server closed successfully');
824
985
  }
986
+ // Close the https server
825
987
  if (this.httpsServer) {
826
988
  this.httpsServer.close();
827
989
  this.httpsServer.removeAllListeners();
828
990
  this.httpsServer = undefined;
829
991
  this.log.debug('Frontend https server closed successfully');
830
992
  }
993
+ // Remove listeners from the express app
831
994
  if (this.expressApp) {
832
995
  this.expressApp.removeAllListeners();
833
996
  this.expressApp = undefined;
834
997
  this.log.debug('Frontend app closed successfully');
835
998
  }
999
+ // Close the WebSocket server
836
1000
  if (this.webSocketServer) {
1001
+ // Close all active connections
837
1002
  this.webSocketServer.clients.forEach((client) => {
838
1003
  if (client.readyState === WebSocket.OPEN) {
839
1004
  client.close();
@@ -850,6 +1015,7 @@ export class Frontend {
850
1015
  this.webSocketServer = undefined;
851
1016
  }
852
1017
  }
1018
+ // Function to format bytes to KB, MB, or GB
853
1019
  formatMemoryUsage = (bytes) => {
854
1020
  if (bytes >= 1024 ** 3) {
855
1021
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -861,6 +1027,7 @@ export class Frontend {
861
1027
  return `${(bytes / 1024).toFixed(2)} KB`;
862
1028
  }
863
1029
  };
1030
+ // Function to format system uptime with only the most significant unit
864
1031
  formatOsUpTime = (seconds) => {
865
1032
  if (seconds >= 86400) {
866
1033
  const days = Math.floor(seconds / 86400);
@@ -876,8 +1043,13 @@ export class Frontend {
876
1043
  }
877
1044
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
878
1045
  };
1046
+ /**
1047
+ * Retrieves the api settings data.
1048
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1049
+ */
879
1050
  async getApiSettings() {
880
1051
  const { lastCpuUsage } = await import('./cli.js');
1052
+ // Update the system information
881
1053
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
882
1054
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
883
1055
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -886,6 +1058,7 @@ export class Frontend {
886
1058
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
887
1059
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
888
1060
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1061
+ // Update the matterbridge information
889
1062
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
890
1063
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
891
1064
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -904,6 +1077,11 @@ export class Frontend {
904
1077
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
905
1078
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
906
1079
  }
1080
+ /**
1081
+ * Retrieves the reachable attribute.
1082
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1083
+ * @returns {boolean} The reachable attribute.
1084
+ */
907
1085
  getReachability(device) {
908
1086
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
909
1087
  return false;
@@ -913,6 +1091,11 @@ export class Frontend {
913
1091
  return true;
914
1092
  return false;
915
1093
  }
1094
+ /**
1095
+ * Retrieves the cluster text description from a given device.
1096
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1097
+ * @returns {string} The attributes description of the cluster servers in the device.
1098
+ */
916
1099
  getClusterTextFromDevice(device) {
917
1100
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
918
1101
  return '';
@@ -953,6 +1136,7 @@ export class Frontend {
953
1136
  };
954
1137
  let attributes = '';
955
1138
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1139
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
956
1140
  if (typeof attributeValue === 'undefined')
957
1141
  return;
958
1142
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1030,8 +1214,13 @@ export class Frontend {
1030
1214
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1031
1215
  attributes += `${getUserLabel(device)} `;
1032
1216
  });
1217
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1033
1218
  return attributes.trimStart().trimEnd();
1034
1219
  }
1220
+ /**
1221
+ * Retrieves the base registered plugins sanitized for res.json().
1222
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1223
+ */
1035
1224
  getBaseRegisteredPlugins() {
1036
1225
  const baseRegisteredPlugins = [];
1037
1226
  for (const plugin of this.matterbridge.plugins) {
@@ -1064,6 +1253,13 @@ export class Frontend {
1064
1253
  }
1065
1254
  return baseRegisteredPlugins;
1066
1255
  }
1256
+ /**
1257
+ * Handles incoming websocket messages for the Matterbridge frontend.
1258
+ *
1259
+ * @param {WebSocket} client - The websocket client that sent the message.
1260
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1261
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1262
+ */
1067
1263
  async wsMessageHandler(client, message) {
1068
1264
  let data;
1069
1265
  try {
@@ -1209,8 +1405,10 @@ export class Frontend {
1209
1405
  else if (data.method === '/api/devices') {
1210
1406
  const devices = [];
1211
1407
  this.matterbridge.devices.forEach(async (device) => {
1408
+ // Filter by pluginName if provided
1212
1409
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1213
1410
  return;
1411
+ // Check if the device has the required properties
1214
1412
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1215
1413
  return;
1216
1414
  const cluster = this.getClusterTextFromDevice(device);
@@ -1294,6 +1492,7 @@ export class Frontend {
1294
1492
  });
1295
1493
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1296
1494
  deviceTypes = [];
1495
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1297
1496
  const name = childEndpoint.endpoint?.id;
1298
1497
  const clusterServers = childEndpoint.getAllClusterServers();
1299
1498
  clusterServers.forEach((clusterServer) => {
@@ -1347,6 +1546,7 @@ export class Frontend {
1347
1546
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select' }));
1348
1547
  return;
1349
1548
  }
1549
+ // const selectDeviceValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectDevice.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1350
1550
  const selectDeviceValues = plugin.platform?.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1351
1551
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectDeviceValues }));
1352
1552
  return;
@@ -1361,6 +1561,7 @@ export class Frontend {
1361
1561
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' }));
1362
1562
  return;
1363
1563
  }
1564
+ // const selectEntityValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectEntity.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1364
1565
  const selectEntityValues = plugin.platform?.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1365
1566
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectEntityValues }));
1366
1567
  return;
@@ -1381,11 +1582,13 @@ export class Frontend {
1381
1582
  if (config.postfix) {
1382
1583
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1383
1584
  }
1585
+ // Add the serial to the whiteList if the whiteList exists and the serial is not already in it
1384
1586
  if (isValidArray(config.whiteList, 1)) {
1385
1587
  if (!config.whiteList.includes(data.params.serial)) {
1386
1588
  config.whiteList.push(data.params.serial);
1387
1589
  }
1388
1590
  }
1591
+ // Remove the serial from the blackList if the blackList exists and the serial is in it
1389
1592
  if (isValidArray(config.blackList, 1)) {
1390
1593
  if (config.blackList.includes(data.params.serial)) {
1391
1594
  config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
@@ -1408,11 +1611,13 @@ export class Frontend {
1408
1611
  if (config.postfix) {
1409
1612
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1410
1613
  }
1614
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1411
1615
  if (isValidArray(config.whiteList, 1)) {
1412
1616
  if (config.whiteList.includes(data.params.serial)) {
1413
1617
  config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
1414
1618
  }
1415
1619
  }
1620
+ // Add the serial to the blackList
1416
1621
  if (isValidArray(config.blackList)) {
1417
1622
  if (!config.blackList.includes(data.params.serial)) {
1418
1623
  config.blackList.push(data.params.serial);
@@ -1436,94 +1641,171 @@ export class Frontend {
1436
1641
  return;
1437
1642
  }
1438
1643
  }
1644
+ /**
1645
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1646
+ *
1647
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1648
+ * @param {string} time - The time string of the message
1649
+ * @param {string} name - The logger name of the message
1650
+ * @param {string} message - The content of the message.
1651
+ */
1439
1652
  wssSendMessage(level, time, name, message) {
1440
1653
  if (!level || !time || !name || !message)
1441
1654
  return;
1655
+ // Remove ANSI escape codes from the message
1656
+ // eslint-disable-next-line no-control-regex
1442
1657
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1658
+ // Remove leading asterisks from the message
1443
1659
  message = message.replace(/^\*+/, '');
1660
+ // Replace all occurrences of \t and \n
1444
1661
  message = message.replace(/[\t\n]/g, '');
1662
+ // Remove non-printable characters
1663
+ // eslint-disable-next-line no-control-regex
1445
1664
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1665
+ // Replace all occurrences of \" with "
1446
1666
  message = message.replace(/\\"/g, '"');
1667
+ // Define the maximum allowed length for continuous characters without a space
1447
1668
  const maxContinuousLength = 100;
1448
1669
  const keepStartLength = 20;
1449
1670
  const keepEndLength = 20;
1671
+ // Split the message into words
1450
1672
  message = message
1451
1673
  .split(' ')
1452
1674
  .map((word) => {
1675
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1453
1676
  if (word.length > maxContinuousLength) {
1454
1677
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1455
1678
  }
1456
1679
  return word;
1457
1680
  })
1458
1681
  .join(' ');
1682
+ // Send the message to all connected clients
1459
1683
  this.webSocketServer?.clients.forEach((client) => {
1460
1684
  if (client.readyState === WebSocket.OPEN) {
1461
1685
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1462
1686
  }
1463
1687
  });
1464
1688
  }
1689
+ /**
1690
+ * Sends a need to refresh WebSocket message to all connected clients.
1691
+ *
1692
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1693
+ * possible values:
1694
+ * - 'matterbridgeLatestVersion'
1695
+ * - 'matterbridgeAdvertise'
1696
+ * - 'online'
1697
+ * - 'offline'
1698
+ * - 'reachability'
1699
+ * - 'settings'
1700
+ * - 'plugins'
1701
+ * - 'devices'
1702
+ * - 'fabrics'
1703
+ * - 'sessions'
1704
+ */
1465
1705
  wssSendRefreshRequired(changed = null) {
1466
1706
  this.log.debug('Sending a refresh required message to all connected clients');
1707
+ // Send the message to all connected clients
1467
1708
  this.webSocketServer?.clients.forEach((client) => {
1468
1709
  if (client.readyState === WebSocket.OPEN) {
1469
1710
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1470
1711
  }
1471
1712
  });
1472
1713
  }
1714
+ /**
1715
+ * Sends a need to restart WebSocket message to all connected clients.
1716
+ *
1717
+ */
1473
1718
  wssSendRestartRequired(snackbar = true) {
1474
1719
  this.log.debug('Sending a restart required message to all connected clients');
1475
1720
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1476
1721
  if (snackbar === true)
1477
1722
  this.wssSendSnackbarMessage(`Restart required`, 0);
1723
+ // Send the message to all connected clients
1478
1724
  this.webSocketServer?.clients.forEach((client) => {
1479
1725
  if (client.readyState === WebSocket.OPEN) {
1480
1726
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1481
1727
  }
1482
1728
  });
1483
1729
  }
1730
+ /**
1731
+ * Sends a need to update WebSocket message to all connected clients.
1732
+ *
1733
+ */
1484
1734
  wssSendUpdateRequired() {
1485
1735
  this.log.debug('Sending an update required message to all connected clients');
1486
1736
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1737
+ // Send the message to all connected clients
1487
1738
  this.webSocketServer?.clients.forEach((client) => {
1488
1739
  if (client.readyState === WebSocket.OPEN) {
1489
1740
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1490
1741
  }
1491
1742
  });
1492
1743
  }
1744
+ /**
1745
+ * Sends a memory update message to all connected clients.
1746
+ *
1747
+ */
1493
1748
  wssSendCpuUpdate(cpuUsage) {
1494
1749
  this.log.debug('Sending a cpu update message to all connected clients');
1750
+ // Send the message to all connected clients
1495
1751
  this.webSocketServer?.clients.forEach((client) => {
1496
1752
  if (client.readyState === WebSocket.OPEN) {
1497
1753
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1498
1754
  }
1499
1755
  });
1500
1756
  }
1757
+ /**
1758
+ * Sends a cpu update message to all connected clients.
1759
+ *
1760
+ */
1501
1761
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1502
1762
  this.log.debug('Sending a memory update message to all connected clients');
1763
+ // Send the message to all connected clients
1503
1764
  this.webSocketServer?.clients.forEach((client) => {
1504
1765
  if (client.readyState === WebSocket.OPEN) {
1505
1766
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1506
1767
  }
1507
1768
  });
1508
1769
  }
1770
+ /**
1771
+ * Sends a memory update message to all connected clients.
1772
+ *
1773
+ */
1509
1774
  wssSendUptimeUpdate(systemUptime, processUptime) {
1510
1775
  this.log.debug('Sending a uptime update message to all connected clients');
1776
+ // Send the message to all connected clients
1511
1777
  this.webSocketServer?.clients.forEach((client) => {
1512
1778
  if (client.readyState === WebSocket.OPEN) {
1513
1779
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1514
1780
  }
1515
1781
  });
1516
1782
  }
1783
+ /**
1784
+ * Sends a cpu update message to all connected clients.
1785
+ * @param {string} message - The message to send.
1786
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1787
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1788
+ *
1789
+ */
1517
1790
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1518
1791
  this.log.debug('Sending a snackbar message to all connected clients');
1792
+ // Send the message to all connected clients
1519
1793
  this.webSocketServer?.clients.forEach((client) => {
1520
1794
  if (client.readyState === WebSocket.OPEN) {
1521
1795
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout, severity } }));
1522
1796
  }
1523
1797
  });
1524
1798
  }
1799
+ /**
1800
+ * Sends a message to all connected clients.
1801
+ * @param {number} id - The message id.
1802
+ * @param {string} method - The message method.
1803
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1804
+ *
1805
+ */
1525
1806
  wssBroadcastMessage(id, method, params) {
1526
1807
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1808
+ // Send the message to all connected clients
1527
1809
  this.webSocketServer?.clients.forEach((client) => {
1528
1810
  if (client.readyState === WebSocket.OPEN) {
1529
1811
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1531,3 +1813,4 @@ export class Frontend {
1531
1813
  });
1532
1814
  }
1533
1815
  }
1816
+ //# sourceMappingURL=frontend.js.map