matterbridge 2.2.5-dev.4 → 2.2.5

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