matterbridge 2.2.8-dev.1 → 2.2.8

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 +2 -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 +221 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +326 -19
  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 +425 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +747 -46
  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 +867 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +733 -8
  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 +285 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +216 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +183 -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 +271 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +262 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/shelly.d.ts +92 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +146 -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 +69 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +76 -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.eb2f3e01.js → main.e11d6bb4.js} +3 -3
  149. package/frontend/build/static/js/{main.eb2f3e01.js.map → main.e11d6bb4.js.map} +1 -1
  150. package/npm-shrinkwrap.json +2 -2
  151. package/package.json +2 -1
  152. /package/frontend/build/static/js/{main.eb2f3e01.js.LICENSE.txt → main.e11d6bb4.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,27 +1,106 @@
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 } from './utils/export.js';
12
39
  import { plg } from './matterbridgeTypes.js';
13
40
  import { hasParameter } from './utils/export.js';
14
41
  import { BridgedDeviceBasicInformation } 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 a shelly system update.
89
+ * check:
90
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
91
+ * perform:
92
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
93
+ * @constant {number}
94
+ */
24
95
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
96
+ /**
97
+ * Websocket message ID indicating a shelly main update.
98
+ * check:
99
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
100
+ * perform:
101
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
102
+ * @constant {number}
103
+ */
25
104
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
26
105
  export class Frontend {
27
106
  matterbridge;
@@ -39,7 +118,7 @@ export class Frontend {
39
118
  memoryTimeout;
40
119
  constructor(matterbridge) {
41
120
  this.matterbridge = matterbridge;
42
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
121
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
43
122
  }
44
123
  set logLevel(logLevel) {
45
124
  this.log.logLevel = logLevel;
@@ -47,13 +126,25 @@ export class Frontend {
47
126
  async start(port = 8283) {
48
127
  this.port = port;
49
128
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
129
+ // Initialize multer with the upload directory
50
130
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
51
131
  await fs.mkdir(uploadDir, { recursive: true });
52
132
  const upload = multer({ dest: uploadDir });
133
+ // Create the express app that serves the frontend
53
134
  this.expressApp = express();
135
+ // Log all requests to the server for debugging
136
+ /*
137
+ this.expressApp.use((req, res, next) => {
138
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
139
+ next();
140
+ });
141
+ */
142
+ // Serve static files from '/static' endpoint
54
143
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
55
144
  if (!hasParameter('ssl')) {
145
+ // Create an HTTP server and attach the express app
56
146
  this.httpServer = createServer(this.expressApp);
147
+ // Listen on the specified port
57
148
  if (hasParameter('ingress')) {
58
149
  this.httpServer.listen(this.port, '0.0.0.0', () => {
59
150
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -67,6 +158,7 @@ export class Frontend {
67
158
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
68
159
  });
69
160
  }
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
162
  this.httpServer.on('error', (error) => {
71
163
  this.log.error(`Frontend http server error listening on ${this.port}`);
72
164
  switch (error.code) {
@@ -82,6 +174,7 @@ export class Frontend {
82
174
  });
83
175
  }
84
176
  else {
177
+ // Load the SSL certificate, the private key and optionally the CA certificate
85
178
  let cert;
86
179
  try {
87
180
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -109,7 +202,9 @@ export class Frontend {
109
202
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
110
203
  }
111
204
  const serverOptions = { cert, key, ca };
205
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
112
206
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
207
+ // Listen on the specified port
113
208
  if (hasParameter('ingress')) {
114
209
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
115
210
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -123,6 +218,7 @@ export class Frontend {
123
218
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
124
219
  });
125
220
  }
221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
222
  this.httpsServer.on('error', (error) => {
127
223
  this.log.error(`Frontend https server error listening on ${this.port}`);
128
224
  switch (error.code) {
@@ -139,16 +235,18 @@ export class Frontend {
139
235
  }
140
236
  if (this.initializeError)
141
237
  return;
238
+ // Create a WebSocket server and attach it to the http or https server
142
239
  const wssPort = this.port;
143
240
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
144
241
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
145
242
  this.webSocketServer.on('connection', (ws, request) => {
146
243
  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";
244
+ // Set the global logger callback for the WebSocketServer
245
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
246
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
247
+ callbackLogLevel = "info" /* LogLevel.INFO */;
248
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
249
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
152
250
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
153
251
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
154
252
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -182,6 +280,7 @@ export class Frontend {
182
280
  this.webSocketServer.on('error', (ws, error) => {
183
281
  this.log.error(`WebSocketServer error: ${error}`);
184
282
  });
283
+ // Subscribe to cli events
185
284
  const { cliEmitter } = await import('./cli.js');
186
285
  cliEmitter.removeAllListeners();
187
286
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -193,6 +292,7 @@ export class Frontend {
193
292
  cliEmitter.on('cpu', (cpuUsage) => {
194
293
  this.wssSendCpuUpdate(cpuUsage);
195
294
  });
295
+ // Endpoint to validate login code
196
296
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
197
297
  const { password } = req.body;
198
298
  this.log.debug('The frontend sent /api/login', password);
@@ -211,23 +311,27 @@ export class Frontend {
211
311
  this.log.warn('/api/login error wrong password');
212
312
  res.json({ valid: false });
213
313
  }
314
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
214
315
  }
215
316
  catch (error) {
216
317
  this.log.error('/api/login error getting password');
217
318
  res.json({ valid: false });
218
319
  }
219
320
  });
321
+ // Endpoint to provide health check
220
322
  this.expressApp.get('/health', (req, res) => {
221
323
  this.log.debug('Express received /health');
222
324
  const healthStatus = {
223
- status: 'ok',
224
- uptime: process.uptime(),
225
- timestamp: new Date().toISOString(),
325
+ status: 'ok', // Indicate service is healthy
326
+ uptime: process.uptime(), // Server uptime in seconds
327
+ timestamp: new Date().toISOString(), // Current timestamp
226
328
  };
227
329
  res.status(200).json(healthStatus);
228
330
  });
331
+ // Endpoint to provide memory usage details
229
332
  this.expressApp.get('/memory', async (req, res) => {
230
333
  this.log.debug('Express received /memory');
334
+ // Memory usage from process
231
335
  const memoryUsageRaw = process.memoryUsage();
232
336
  const memoryUsage = {
233
337
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -236,10 +340,13 @@ export class Frontend {
236
340
  external: this.formatMemoryUsage(memoryUsageRaw.external),
237
341
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
238
342
  };
343
+ // V8 heap statistics
239
344
  const { default: v8 } = await import('node:v8');
240
345
  const heapStatsRaw = v8.getHeapStatistics();
241
346
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
347
+ // Format heapStats
242
348
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
349
+ // Format heapSpaces
243
350
  const heapSpaces = heapSpacesRaw.map((space) => ({
244
351
  ...space,
245
352
  space_size: this.formatMemoryUsage(space.space_size),
@@ -257,6 +364,7 @@ export class Frontend {
257
364
  };
258
365
  res.status(200).json(memoryReport);
259
366
  });
367
+ // Endpoint to start advertising the server node
260
368
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
261
369
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
262
370
  if (pairingCodes) {
@@ -267,18 +375,22 @@ export class Frontend {
267
375
  res.status(500).json({ error: 'Failed to generate pairing codes' });
268
376
  }
269
377
  });
378
+ // Endpoint to provide settings
270
379
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
271
380
  this.log.debug('The frontend sent /api/settings');
272
381
  res.json(await this.getApiSettings());
273
382
  });
383
+ // Endpoint to provide plugins
274
384
  this.expressApp.get('/api/plugins', async (req, res) => {
275
385
  this.log.debug('The frontend sent /api/plugins');
276
386
  res.json(this.getBaseRegisteredPlugins());
277
387
  });
388
+ // Endpoint to provide devices
278
389
  this.expressApp.get('/api/devices', (req, res) => {
279
390
  this.log.debug('The frontend sent /api/devices');
280
391
  const devices = [];
281
392
  this.matterbridge.devices.forEach(async (device) => {
393
+ // Check if the device has the required properties
282
394
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
283
395
  return;
284
396
  const cluster = this.getClusterTextFromDevice(device);
@@ -297,6 +409,7 @@ export class Frontend {
297
409
  });
298
410
  res.json(devices);
299
411
  });
412
+ // Endpoint to provide the cluster servers of the devices
300
413
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
301
414
  const selectedPluginName = req.params.selectedPluginName;
302
415
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -369,6 +482,7 @@ export class Frontend {
369
482
  });
370
483
  res.json(data);
371
484
  });
485
+ // Endpoint to view the log
372
486
  this.expressApp.get('/api/view-log', async (req, res) => {
373
487
  this.log.debug('The frontend sent /api/log');
374
488
  try {
@@ -381,10 +495,12 @@ export class Frontend {
381
495
  res.status(500).send('Error reading log file');
382
496
  }
383
497
  });
498
+ // Endpoint to download the matterbridge log
384
499
  this.expressApp.get('/api/download-mblog', async (req, res) => {
385
500
  this.log.debug('The frontend sent /api/download-mblog');
386
501
  try {
387
502
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
503
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
388
504
  }
389
505
  catch (error) {
390
506
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -396,10 +512,12 @@ export class Frontend {
396
512
  }
397
513
  });
398
514
  });
515
+ // Endpoint to download the matter log
399
516
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
400
517
  this.log.debug('The frontend sent /api/download-mjlog');
401
518
  try {
402
519
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
520
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
403
521
  }
404
522
  catch (error) {
405
523
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -411,10 +529,12 @@ export class Frontend {
411
529
  }
412
530
  });
413
531
  });
532
+ // Endpoint to download the matter log
414
533
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
415
534
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
416
535
  try {
417
536
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
537
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
418
538
  }
419
539
  catch (error) {
420
540
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
@@ -426,6 +546,7 @@ export class Frontend {
426
546
  }
427
547
  });
428
548
  });
549
+ // Endpoint to download the matter storage file
429
550
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
430
551
  this.log.debug('The frontend sent /api/download-mjstorage');
431
552
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -436,6 +557,7 @@ export class Frontend {
436
557
  }
437
558
  });
438
559
  });
560
+ // Endpoint to download the matterbridge storage directory
439
561
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
440
562
  this.log.debug('The frontend sent /api/download-mbstorage');
441
563
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -446,6 +568,7 @@ export class Frontend {
446
568
  }
447
569
  });
448
570
  });
571
+ // Endpoint to download the matterbridge plugin directory
449
572
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
450
573
  this.log.debug('The frontend sent /api/download-pluginstorage');
451
574
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -456,9 +579,11 @@ export class Frontend {
456
579
  }
457
580
  });
458
581
  });
582
+ // Endpoint to download the matterbridge plugin config files
459
583
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
460
584
  this.log.debug('The frontend sent /api/download-pluginconfig');
461
585
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
586
+ // 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')));
462
587
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
463
588
  if (error) {
464
589
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -466,6 +591,7 @@ export class Frontend {
466
591
  }
467
592
  });
468
593
  });
594
+ // Endpoint to download the matterbridge plugin config files
469
595
  this.expressApp.get('/api/download-backup', async (req, res) => {
470
596
  this.log.debug('The frontend sent /api/download-backup');
471
597
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -475,6 +601,7 @@ export class Frontend {
475
601
  }
476
602
  });
477
603
  });
604
+ // Endpoint to receive commands
478
605
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
479
606
  const command = req.params.command;
480
607
  let param = req.params.param;
@@ -484,13 +611,15 @@ export class Frontend {
484
611
  return;
485
612
  }
486
613
  this.log.debug(`Received frontend command: ${command}:${param}`);
614
+ // Handle the command setpassword from Settings
487
615
  if (command === 'setpassword') {
488
- const password = param.slice(1, -1);
616
+ const password = param.slice(1, -1); // Remove the first and last characters
489
617
  this.log.debug('setpassword', param, password);
490
618
  await this.matterbridge.nodeContext?.set('password', password);
491
619
  res.json({ message: 'Command received' });
492
620
  return;
493
621
  }
622
+ // Handle the command setbridgemode from Settings
494
623
  if (command === 'setbridgemode') {
495
624
  this.log.debug(`setbridgemode: ${param}`);
496
625
  this.wssSendRestartRequired();
@@ -498,6 +627,7 @@ export class Frontend {
498
627
  res.json({ message: 'Command received' });
499
628
  return;
500
629
  }
630
+ // Handle the command backup from Settings
501
631
  if (command === 'backup') {
502
632
  this.log.notice(`Prepairing the backup...`);
503
633
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -506,31 +636,33 @@ export class Frontend {
506
636
  res.json({ message: 'Command received' });
507
637
  return;
508
638
  }
639
+ // Handle the command setmbloglevel from Settings
509
640
  if (command === 'setmbloglevel') {
510
641
  this.log.debug('Matterbridge log level:', param);
511
642
  if (param === 'Debug') {
512
- this.log.logLevel = "debug";
643
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
513
644
  }
514
645
  else if (param === 'Info') {
515
- this.log.logLevel = "info";
646
+ this.log.logLevel = "info" /* LogLevel.INFO */;
516
647
  }
517
648
  else if (param === 'Notice') {
518
- this.log.logLevel = "notice";
649
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
519
650
  }
520
651
  else if (param === 'Warn') {
521
- this.log.logLevel = "warn";
652
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
522
653
  }
523
654
  else if (param === 'Error') {
524
- this.log.logLevel = "error";
655
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
525
656
  }
526
657
  else if (param === 'Fatal') {
527
- this.log.logLevel = "fatal";
658
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
528
659
  }
529
660
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
530
661
  await this.matterbridge.setLogLevel(this.log.logLevel);
531
662
  res.json({ message: 'Command received' });
532
663
  return;
533
664
  }
665
+ // Handle the command setmbloglevel from Settings
534
666
  if (command === 'setmjloglevel') {
535
667
  this.log.debug('Matter.js log level:', param);
536
668
  if (param === 'Debug') {
@@ -555,6 +687,7 @@ export class Frontend {
555
687
  res.json({ message: 'Command received' });
556
688
  return;
557
689
  }
690
+ // Handle the command setmdnsinterface from Settings
558
691
  if (command === 'setmdnsinterface') {
559
692
  if (param === 'json' && isValidString(req.body.value)) {
560
693
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = req.body.value;
@@ -564,6 +697,7 @@ export class Frontend {
564
697
  res.json({ message: 'Command received' });
565
698
  return;
566
699
  }
700
+ // Handle the command setipv4address from Settings
567
701
  if (command === 'setipv4address') {
568
702
  if (param === 'json' && isValidString(req.body.value)) {
569
703
  this.log.debug(`Matter.js ipv4 address: ${req.body.value === '' ? 'all ipv4 addresses' : req.body.value}`);
@@ -573,6 +707,7 @@ export class Frontend {
573
707
  res.json({ message: 'Command received' });
574
708
  return;
575
709
  }
710
+ // Handle the command setipv6address from Settings
576
711
  if (command === 'setipv6address') {
577
712
  if (param === 'json' && isValidString(req.body.value)) {
578
713
  this.log.debug(`Matter.js ipv6 address: ${req.body.value === '' ? 'all ipv6 addresses' : req.body.value}`);
@@ -582,6 +717,7 @@ export class Frontend {
582
717
  res.json({ message: 'Command received' });
583
718
  return;
584
719
  }
720
+ // Handle the command setmatterport from Settings
585
721
  if (command === 'setmatterport') {
586
722
  const port = Math.min(Math.max(parseInt(req.body.value), 5540), 5560);
587
723
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -590,6 +726,7 @@ export class Frontend {
590
726
  res.json({ message: 'Command received' });
591
727
  return;
592
728
  }
729
+ // Handle the command setmatterdiscriminator from Settings
593
730
  if (command === 'setmatterdiscriminator') {
594
731
  const discriminator = Math.min(Math.max(parseInt(req.body.value), 1000), 4095);
595
732
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -598,6 +735,7 @@ export class Frontend {
598
735
  res.json({ message: 'Command received' });
599
736
  return;
600
737
  }
738
+ // Handle the command setmatterpasscode from Settings
601
739
  if (command === 'setmatterpasscode') {
602
740
  const passcode = Math.min(Math.max(parseInt(req.body.value), 10000000), 90000000);
603
741
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -606,17 +744,20 @@ export class Frontend {
606
744
  res.json({ message: 'Command received' });
607
745
  return;
608
746
  }
747
+ // Handle the command setmbloglevel from Settings
609
748
  if (command === 'setmblogfile') {
610
749
  this.log.debug('Matterbridge file log:', param);
611
750
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
612
751
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
752
+ // Create the file logger for matterbridge
613
753
  if (param === 'true')
614
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
754
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
615
755
  else
616
756
  AnsiLogger.setGlobalLogfile(undefined);
617
757
  res.json({ message: 'Command received' });
618
758
  return;
619
759
  }
760
+ // Handle the command setmbloglevel from Settings
620
761
  if (command === 'setmjlogfile') {
621
762
  this.log.debug('Matter file log:', param);
622
763
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -643,40 +784,48 @@ export class Frontend {
643
784
  res.json({ message: 'Command received' });
644
785
  return;
645
786
  }
787
+ // Handle the command unregister from Settings
646
788
  if (command === 'unregister') {
647
789
  await this.matterbridge.unregisterAndShutdownProcess();
648
790
  res.json({ message: 'Command received' });
649
791
  return;
650
792
  }
793
+ // Handle the command reset from Settings
651
794
  if (command === 'reset') {
652
795
  await this.matterbridge.shutdownProcessAndReset();
653
796
  res.json({ message: 'Command received' });
654
797
  return;
655
798
  }
799
+ // Handle the command factoryreset from Settings
656
800
  if (command === 'factoryreset') {
657
801
  await this.matterbridge.shutdownProcessAndFactoryReset();
658
802
  res.json({ message: 'Command received' });
659
803
  return;
660
804
  }
805
+ // Handle the command shutdown from Header
661
806
  if (command === 'shutdown') {
662
807
  await this.matterbridge.shutdownProcess();
663
808
  res.json({ message: 'Command received' });
664
809
  return;
665
810
  }
811
+ // Handle the command restart from Header
666
812
  if (command === 'restart') {
667
813
  await this.matterbridge.restartProcess();
668
814
  res.json({ message: 'Command received' });
669
815
  return;
670
816
  }
817
+ // Handle the command update from Header
671
818
  if (command === 'update') {
672
819
  await this.matterbridge.updateProcess();
673
820
  this.wssSendRestartRequired();
674
821
  res.json({ message: 'Command received' });
675
822
  return;
676
823
  }
824
+ // Handle the command saveconfig from Home
677
825
  if (command === 'saveconfig') {
678
826
  param = param.replace(/\*/g, '\\');
679
827
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
828
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
680
829
  if (!this.matterbridge.plugins.has(param)) {
681
830
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
682
831
  }
@@ -691,6 +840,7 @@ export class Frontend {
691
840
  res.json({ message: 'Command received' });
692
841
  return;
693
842
  }
843
+ // Handle the command installplugin from Home
694
844
  if (command === 'installplugin') {
695
845
  param = param.replace(/\*/g, '\\');
696
846
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
@@ -699,6 +849,7 @@ export class Frontend {
699
849
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
700
850
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
701
851
  this.wssSendSnackbarMessage(`Installed package ${param}`, 10, 'success');
852
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
702
853
  }
703
854
  catch (error) {
704
855
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
@@ -706,17 +857,22 @@ export class Frontend {
706
857
  }
707
858
  this.wssSendRestartRequired();
708
859
  param = param.split('@')[0];
860
+ // Also add the plugin to matterbridge so no return!
709
861
  if (param === 'matterbridge') {
862
+ // 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
710
863
  res.json({ message: 'Command received' });
711
864
  return;
712
865
  }
713
866
  }
867
+ // Handle the command addplugin from Home
714
868
  if (command === 'addplugin' || command === 'installplugin') {
715
869
  param = param.replace(/\*/g, '\\');
716
870
  const plugin = await this.matterbridge.plugins.add(param);
717
871
  if (plugin) {
718
872
  this.wssSendSnackbarMessage(`Added plugin ${param}`);
719
873
  if (this.matterbridge.bridgeMode === 'childbridge') {
874
+ // 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
875
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
720
876
  this.matterbridge.createDynamicPlugin(plugin, true);
721
877
  }
722
878
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
@@ -726,13 +882,14 @@ export class Frontend {
726
882
  res.json({ message: 'Command received' });
727
883
  return;
728
884
  }
885
+ // Handle the command removeplugin from Home
729
886
  if (command === 'removeplugin') {
730
887
  if (!this.matterbridge.plugins.has(param)) {
731
888
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
732
889
  }
733
890
  else {
734
891
  const plugin = this.matterbridge.plugins.get(param);
735
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
892
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
736
893
  await this.matterbridge.plugins.remove(param);
737
894
  this.wssSendSnackbarMessage(`Removed plugin ${param}`);
738
895
  this.wssSendRefreshRequired('plugins');
@@ -740,6 +897,7 @@ export class Frontend {
740
897
  res.json({ message: 'Command received' });
741
898
  return;
742
899
  }
900
+ // Handle the command enableplugin from Home
743
901
  if (command === 'enableplugin') {
744
902
  if (!this.matterbridge.plugins.has(param)) {
745
903
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -758,6 +916,7 @@ export class Frontend {
758
916
  await this.matterbridge.plugins.enable(param);
759
917
  this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
760
918
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
919
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
761
920
  this.matterbridge.createDynamicPlugin(plugin, true);
762
921
  }
763
922
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
@@ -768,6 +927,7 @@ export class Frontend {
768
927
  res.json({ message: 'Command received' });
769
928
  return;
770
929
  }
930
+ // Handle the command disableplugin from Home
771
931
  if (command === 'disableplugin') {
772
932
  if (!this.matterbridge.plugins.has(param)) {
773
933
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -775,7 +935,7 @@ export class Frontend {
775
935
  else {
776
936
  const plugin = this.matterbridge.plugins.get(param);
777
937
  if (plugin && plugin.enabled) {
778
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
938
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
779
939
  await this.matterbridge.plugins.disable(param);
780
940
  this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
781
941
  this.wssSendRefreshRequired('plugins');
@@ -794,10 +954,13 @@ export class Frontend {
794
954
  return;
795
955
  }
796
956
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`);
957
+ // Define the path where the plugin file will be saved
797
958
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
798
959
  try {
960
+ // Move the uploaded file to the specified path
799
961
  await fs.rename(file.path, filePath);
800
962
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
963
+ // Install the plugin package
801
964
  if (filename.endsWith('.tgz')) {
802
965
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
803
966
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -814,6 +977,7 @@ export class Frontend {
814
977
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
815
978
  }
816
979
  });
980
+ // Fallback for routing (must be the last route)
817
981
  this.expressApp.get('*', (req, res) => {
818
982
  this.log.debug('The frontend sent:', req.url);
819
983
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -822,24 +986,31 @@ export class Frontend {
822
986
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
823
987
  }
824
988
  async stop() {
989
+ // Remove all listeners from the cliEmitter
990
+ // cliEmitter.removeAllListeners();
991
+ // Close the http server
825
992
  if (this.httpServer) {
826
993
  this.httpServer.close();
827
994
  this.httpServer.removeAllListeners();
828
995
  this.httpServer = undefined;
829
996
  this.log.debug('Frontend http server closed successfully');
830
997
  }
998
+ // Close the https server
831
999
  if (this.httpsServer) {
832
1000
  this.httpsServer.close();
833
1001
  this.httpsServer.removeAllListeners();
834
1002
  this.httpsServer = undefined;
835
1003
  this.log.debug('Frontend https server closed successfully');
836
1004
  }
1005
+ // Remove listeners from the express app
837
1006
  if (this.expressApp) {
838
1007
  this.expressApp.removeAllListeners();
839
1008
  this.expressApp = undefined;
840
1009
  this.log.debug('Frontend app closed successfully');
841
1010
  }
1011
+ // Close the WebSocket server
842
1012
  if (this.webSocketServer) {
1013
+ // Close all active connections
843
1014
  this.webSocketServer.clients.forEach((client) => {
844
1015
  if (client.readyState === WebSocket.OPEN) {
845
1016
  client.close();
@@ -856,6 +1027,7 @@ export class Frontend {
856
1027
  this.webSocketServer = undefined;
857
1028
  }
858
1029
  }
1030
+ // Function to format bytes to KB, MB, or GB
859
1031
  formatMemoryUsage = (bytes) => {
860
1032
  if (bytes >= 1024 ** 3) {
861
1033
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -867,6 +1039,7 @@ export class Frontend {
867
1039
  return `${(bytes / 1024).toFixed(2)} KB`;
868
1040
  }
869
1041
  };
1042
+ // Function to format system uptime with only the most significant unit
870
1043
  formatOsUpTime = (seconds) => {
871
1044
  if (seconds >= 86400) {
872
1045
  const days = Math.floor(seconds / 86400);
@@ -882,8 +1055,13 @@ export class Frontend {
882
1055
  }
883
1056
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
884
1057
  };
1058
+ /**
1059
+ * Retrieves the api settings data.
1060
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1061
+ */
885
1062
  async getApiSettings() {
886
1063
  const { lastCpuUsage } = await import('./cli.js');
1064
+ // Update the system information
887
1065
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
888
1066
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
889
1067
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -892,6 +1070,7 @@ export class Frontend {
892
1070
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
893
1071
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
894
1072
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1073
+ // Update the matterbridge information
895
1074
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
896
1075
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
897
1076
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -910,6 +1089,11 @@ export class Frontend {
910
1089
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
911
1090
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
912
1091
  }
1092
+ /**
1093
+ * Retrieves the reachable attribute.
1094
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1095
+ * @returns {boolean} The reachable attribute.
1096
+ */
913
1097
  getReachability(device) {
914
1098
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
915
1099
  return false;
@@ -919,6 +1103,11 @@ export class Frontend {
919
1103
  return true;
920
1104
  return false;
921
1105
  }
1106
+ /**
1107
+ * Retrieves the cluster text description from a given device.
1108
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1109
+ * @returns {string} The attributes description of the cluster servers in the device.
1110
+ */
922
1111
  getClusterTextFromDevice(device) {
923
1112
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
924
1113
  return '';
@@ -959,6 +1148,7 @@ export class Frontend {
959
1148
  };
960
1149
  let attributes = '';
961
1150
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1151
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
962
1152
  if (typeof attributeValue === 'undefined')
963
1153
  return;
964
1154
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1036,8 +1226,13 @@ export class Frontend {
1036
1226
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1037
1227
  attributes += `${getUserLabel(device)} `;
1038
1228
  });
1229
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1039
1230
  return attributes.trimStart().trimEnd();
1040
1231
  }
1232
+ /**
1233
+ * Retrieves the base registered plugins sanitized for res.json().
1234
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1235
+ */
1041
1236
  getBaseRegisteredPlugins() {
1042
1237
  const baseRegisteredPlugins = [];
1043
1238
  for (const plugin of this.matterbridge.plugins) {
@@ -1074,6 +1269,13 @@ export class Frontend {
1074
1269
  }
1075
1270
  return baseRegisteredPlugins;
1076
1271
  }
1272
+ /**
1273
+ * Handles incoming websocket messages for the Matterbridge frontend.
1274
+ *
1275
+ * @param {WebSocket} client - The websocket client that sent the message.
1276
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1277
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1278
+ */
1077
1279
  async wsMessageHandler(client, message) {
1078
1280
  let data;
1079
1281
  try {
@@ -1229,8 +1431,10 @@ export class Frontend {
1229
1431
  else if (data.method === '/api/devices') {
1230
1432
  const devices = [];
1231
1433
  this.matterbridge.devices.forEach(async (device) => {
1434
+ // Filter by pluginName if provided
1232
1435
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1233
1436
  return;
1437
+ // Check if the device has the required properties
1234
1438
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1235
1439
  return;
1236
1440
  const cluster = this.getClusterTextFromDevice(device);
@@ -1314,6 +1518,7 @@ export class Frontend {
1314
1518
  });
1315
1519
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1316
1520
  deviceTypes = [];
1521
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1317
1522
  const name = childEndpoint.endpoint?.id;
1318
1523
  const clusterServers = childEndpoint.getAllClusterServers();
1319
1524
  clusterServers.forEach((clusterServer) => {
@@ -1367,6 +1572,7 @@ export class Frontend {
1367
1572
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select' }));
1368
1573
  return;
1369
1574
  }
1575
+ // const selectDeviceValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectDevice.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1370
1576
  const selectDeviceValues = plugin.platform?.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1371
1577
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectDeviceValues }));
1372
1578
  return;
@@ -1381,6 +1587,7 @@ export class Frontend {
1381
1587
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' }));
1382
1588
  return;
1383
1589
  }
1590
+ // const selectEntityValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectEntity.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1384
1591
  const selectEntityValues = plugin.platform?.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1385
1592
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectEntityValues }));
1386
1593
  return;
@@ -1412,6 +1619,7 @@ export class Frontend {
1412
1619
  return;
1413
1620
  }
1414
1621
  const config = plugin.configJson;
1622
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1415
1623
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1416
1624
  this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1417
1625
  if (select === 'serial')
@@ -1419,9 +1627,11 @@ export class Frontend {
1419
1627
  if (select === 'name')
1420
1628
  this.log.info(`Selected device name ${data.params.name}`);
1421
1629
  if (config && select && (select === 'serial' || select === 'name')) {
1630
+ // Remove postfix from the serial if it exists
1422
1631
  if (config.postfix) {
1423
1632
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1424
1633
  }
1634
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1425
1635
  if (isValidArray(config.whiteList, 1)) {
1426
1636
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1427
1637
  config.whiteList.push(data.params.serial);
@@ -1430,6 +1640,7 @@ export class Frontend {
1430
1640
  config.whiteList.push(data.params.name);
1431
1641
  }
1432
1642
  }
1643
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1433
1644
  if (isValidArray(config.blackList, 1)) {
1434
1645
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1435
1646
  config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
@@ -1451,6 +1662,7 @@ export class Frontend {
1451
1662
  return;
1452
1663
  }
1453
1664
  const config = plugin.configJson;
1665
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1454
1666
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1455
1667
  this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1456
1668
  if (select === 'serial')
@@ -1461,6 +1673,7 @@ export class Frontend {
1461
1673
  if (config.postfix) {
1462
1674
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1463
1675
  }
1676
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1464
1677
  if (isValidArray(config.whiteList, 1)) {
1465
1678
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1466
1679
  config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
@@ -1469,6 +1682,7 @@ export class Frontend {
1469
1682
  config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
1470
1683
  }
1471
1684
  }
1685
+ // Add the serial to the blackList
1472
1686
  if (isValidArray(config.blackList)) {
1473
1687
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1474
1688
  config.blackList.push(data.params.serial);
@@ -1495,102 +1709,194 @@ export class Frontend {
1495
1709
  return;
1496
1710
  }
1497
1711
  }
1712
+ /**
1713
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1714
+ *
1715
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1716
+ * @param {string} time - The time string of the message
1717
+ * @param {string} name - The logger name of the message
1718
+ * @param {string} message - The content of the message.
1719
+ */
1498
1720
  wssSendMessage(level, time, name, message) {
1499
1721
  if (!level || !time || !name || !message)
1500
1722
  return;
1723
+ // Remove ANSI escape codes from the message
1724
+ // eslint-disable-next-line no-control-regex
1501
1725
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1726
+ // Remove leading asterisks from the message
1502
1727
  message = message.replace(/^\*+/, '');
1728
+ // Replace all occurrences of \t and \n
1503
1729
  message = message.replace(/[\t\n]/g, '');
1730
+ // Remove non-printable characters
1731
+ // eslint-disable-next-line no-control-regex
1504
1732
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1733
+ // Replace all occurrences of \" with "
1505
1734
  message = message.replace(/\\"/g, '"');
1735
+ // Define the maximum allowed length for continuous characters without a space
1506
1736
  const maxContinuousLength = 100;
1507
1737
  const keepStartLength = 20;
1508
1738
  const keepEndLength = 20;
1739
+ // Split the message into words
1509
1740
  message = message
1510
1741
  .split(' ')
1511
1742
  .map((word) => {
1743
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1512
1744
  if (word.length > maxContinuousLength) {
1513
1745
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1514
1746
  }
1515
1747
  return word;
1516
1748
  })
1517
1749
  .join(' ');
1750
+ // Send the message to all connected clients
1518
1751
  this.webSocketServer?.clients.forEach((client) => {
1519
1752
  if (client.readyState === WebSocket.OPEN) {
1520
1753
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1521
1754
  }
1522
1755
  });
1523
1756
  }
1757
+ /**
1758
+ * Sends a need to refresh WebSocket message to all connected clients.
1759
+ *
1760
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1761
+ * possible values:
1762
+ * - 'matterbridgeLatestVersion'
1763
+ * - 'matterbridgeAdvertise'
1764
+ * - 'online'
1765
+ * - 'offline'
1766
+ * - 'reachability'
1767
+ * - 'settings'
1768
+ * - 'plugins'
1769
+ * - 'devices'
1770
+ * - 'fabrics'
1771
+ * - 'sessions'
1772
+ */
1524
1773
  wssSendRefreshRequired(changed = null) {
1525
1774
  this.log.debug('Sending a refresh required message to all connected clients');
1775
+ // Send the message to all connected clients
1526
1776
  this.webSocketServer?.clients.forEach((client) => {
1527
1777
  if (client.readyState === WebSocket.OPEN) {
1528
1778
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1529
1779
  }
1530
1780
  });
1531
1781
  }
1782
+ /**
1783
+ * Sends a need to restart WebSocket message to all connected clients.
1784
+ *
1785
+ */
1532
1786
  wssSendRestartRequired(snackbar = true) {
1533
1787
  this.log.debug('Sending a restart required message to all connected clients');
1534
1788
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1535
1789
  if (snackbar === true)
1536
1790
  this.wssSendSnackbarMessage(`Restart required`, 0);
1791
+ // Send the message to all connected clients
1537
1792
  this.webSocketServer?.clients.forEach((client) => {
1538
1793
  if (client.readyState === WebSocket.OPEN) {
1539
1794
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1540
1795
  }
1541
1796
  });
1542
1797
  }
1798
+ /**
1799
+ * Sends a need to update WebSocket message to all connected clients.
1800
+ *
1801
+ */
1543
1802
  wssSendUpdateRequired() {
1544
1803
  this.log.debug('Sending an update required message to all connected clients');
1545
1804
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1805
+ // Send the message to all connected clients
1546
1806
  this.webSocketServer?.clients.forEach((client) => {
1547
1807
  if (client.readyState === WebSocket.OPEN) {
1548
1808
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1549
1809
  }
1550
1810
  });
1551
1811
  }
1812
+ /**
1813
+ * Sends a memory update message to all connected clients.
1814
+ *
1815
+ */
1552
1816
  wssSendCpuUpdate(cpuUsage) {
1553
1817
  this.log.debug('Sending a cpu update message to all connected clients');
1818
+ // Send the message to all connected clients
1554
1819
  this.webSocketServer?.clients.forEach((client) => {
1555
1820
  if (client.readyState === WebSocket.OPEN) {
1556
1821
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1557
1822
  }
1558
1823
  });
1559
1824
  }
1825
+ /**
1826
+ * Sends a cpu update message to all connected clients.
1827
+ *
1828
+ */
1560
1829
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1561
1830
  this.log.debug('Sending a memory update message to all connected clients');
1831
+ // Send the message to all connected clients
1562
1832
  this.webSocketServer?.clients.forEach((client) => {
1563
1833
  if (client.readyState === WebSocket.OPEN) {
1564
1834
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1565
1835
  }
1566
1836
  });
1567
1837
  }
1838
+ /**
1839
+ * Sends a memory update message to all connected clients.
1840
+ *
1841
+ */
1568
1842
  wssSendUptimeUpdate(systemUptime, processUptime) {
1569
1843
  this.log.debug('Sending a uptime update message to all connected clients');
1844
+ // Send the message to all connected clients
1570
1845
  this.webSocketServer?.clients.forEach((client) => {
1571
1846
  if (client.readyState === WebSocket.OPEN) {
1572
1847
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1573
1848
  }
1574
1849
  });
1575
1850
  }
1851
+ /**
1852
+ * Sends a cpu update message to all connected clients.
1853
+ * @param {string} message - The message to send.
1854
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1855
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1856
+ *
1857
+ */
1576
1858
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1577
1859
  this.log.debug('Sending a snackbar message to all connected clients');
1860
+ // Send the message to all connected clients
1578
1861
  this.webSocketServer?.clients.forEach((client) => {
1579
1862
  if (client.readyState === WebSocket.OPEN) {
1580
1863
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout, severity } }));
1581
1864
  }
1582
1865
  });
1583
1866
  }
1867
+ /**
1868
+ * Sends an attribute update message to all connected WebSocket clients.
1869
+ *
1870
+ * @param {string | undefined} plugin - The name of the plugin.
1871
+ * @param {string | undefined} serialNumber - The serial number of the device.
1872
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
1873
+ * @param {string} cluster - The cluster name where the attribute belongs.
1874
+ * @param {string} attribute - The name of the attribute that changed.
1875
+ * @param {number | string | boolean} value - The new value of the attribute.
1876
+ *
1877
+ * @remarks
1878
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1879
+ * with the updated attribute information.
1880
+ */
1584
1881
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1585
1882
  this.log.debug('Sending an attribute update message to all connected clients');
1883
+ // Send the message to all connected clients
1586
1884
  this.webSocketServer?.clients.forEach((client) => {
1587
1885
  if (client.readyState === WebSocket.OPEN) {
1588
1886
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1589
1887
  }
1590
1888
  });
1591
1889
  }
1890
+ /**
1891
+ * Sends a message to all connected clients.
1892
+ * @param {number} id - The message id.
1893
+ * @param {string} method - The message method.
1894
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1895
+ *
1896
+ */
1592
1897
  wssBroadcastMessage(id, method, params) {
1593
1898
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1899
+ // Send the message to all connected clients
1594
1900
  this.webSocketServer?.clients.forEach((client) => {
1595
1901
  if (client.readyState === WebSocket.OPEN) {
1596
1902
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1598,3 +1904,4 @@ export class Frontend {
1598
1904
  });
1599
1905
  }
1600
1906
  }
1907
+ //# sourceMappingURL=frontend.js.map