matterbridge 2.2.0-dev.8 → 2.2.0

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 (147) 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 +24 -6
  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 +172 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +270 -23
  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 +411 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +719 -47
  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 +692 -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 +181 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +140 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +174 -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/npm-shrinkwrap.json +2 -2
  147. package/package.json +2 -1
package/dist/frontend.js CHANGED
@@ -1,4 +1,28 @@
1
- import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat } from '@matter/main';
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @date 2025-01-13
7
+ * @version 1.0.2
8
+ *
9
+ * Copyright 2025, 2026, 2027 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // @matter
24
+ 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 'https';
4
28
  import express from 'express';
@@ -6,19 +30,70 @@ import WebSocket, { WebSocketServer } from 'ws';
6
30
  import os from 'node:os';
7
31
  import path from 'node:path';
8
32
  import { promises as fs } from 'node:fs';
33
+ // AnsiLogger module
9
34
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
35
+ // Matterbridge
10
36
  import { createZip, deepCopy, isValidNumber, isValidObject, isValidString } from './utils/export.js';
11
37
  import { plg } from './matterbridgeTypes.js';
12
38
  import { hasParameter } from './utils/export.js';
39
+ /**
40
+ * Websocket message ID for logging.
41
+ * @constant {number}
42
+ */
13
43
  export const WS_ID_LOG = 0;
44
+ /**
45
+ * Websocket message ID indicating a refresh is needed.
46
+ * @constant {number}
47
+ */
14
48
  export const WS_ID_REFRESH_NEEDED = 1;
49
+ /**
50
+ * Websocket message ID indicating a restart is needed.
51
+ * @constant {number}
52
+ */
15
53
  export const WS_ID_RESTART_NEEDED = 2;
54
+ /**
55
+ * Websocket message ID indicating a cpu update.
56
+ * @constant {number}
57
+ */
16
58
  export const WS_ID_CPU_UPDATE = 3;
59
+ /**
60
+ * Websocket message ID indicating a memory update.
61
+ * @constant {number}
62
+ */
17
63
  export const WS_ID_MEMORY_UPDATE = 4;
64
+ /**
65
+ * Websocket message ID indicating an uptime update.
66
+ * @constant {number}
67
+ */
18
68
  export const WS_ID_UPTIME_UPDATE = 5;
69
+ /**
70
+ * Websocket message ID indicating a memory update.
71
+ * @constant {number}
72
+ */
19
73
  export const WS_ID_SNACKBAR = 6;
74
+ /**
75
+ * Websocket message ID indicating a shelly system update.
76
+ * check:
77
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
78
+ * perform:
79
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
80
+ * @constant {number}
81
+ */
20
82
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
83
+ /**
84
+ * Websocket message ID indicating a shelly main update.
85
+ * check:
86
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
87
+ * perform:
88
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
89
+ * @constant {number}
90
+ */
21
91
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
92
+ /**
93
+ * Initializes the frontend of Matterbridge.
94
+ *
95
+ * @param port The port number to run the frontend server on. Default is 8283.
96
+ */
22
97
  export class Frontend {
23
98
  matterbridge;
24
99
  log;
@@ -35,7 +110,7 @@ export class Frontend {
35
110
  memoryTimeout;
36
111
  constructor(matterbridge) {
37
112
  this.matterbridge = matterbridge;
38
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
113
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
39
114
  }
40
115
  set logLevel(logLevel) {
41
116
  this.log.logLevel = logLevel;
@@ -43,10 +118,21 @@ export class Frontend {
43
118
  async start(port = 8283) {
44
119
  this.port = port;
45
120
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
121
+ // Create the express app that serves the frontend
46
122
  this.expressApp = express();
123
+ // Log all requests to the server for debugging
124
+ /*
125
+ this.expressApp.use((req, res, next) => {
126
+ this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
127
+ next();
128
+ });
129
+ */
130
+ // Serve static files from '/static' endpoint
47
131
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
48
132
  if (!hasParameter('ssl')) {
133
+ // Create an HTTP server and attach the express app
49
134
  this.httpServer = createServer(this.expressApp);
135
+ // Listen on the specified port
50
136
  if (hasParameter('ingress')) {
51
137
  this.httpServer.listen(this.port, '0.0.0.0', () => {
52
138
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -60,6 +146,7 @@ export class Frontend {
60
146
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
61
147
  });
62
148
  }
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
150
  this.httpServer.on('error', (error) => {
64
151
  this.log.error(`Frontend http server error listening on ${this.port}`);
65
152
  switch (error.code) {
@@ -75,6 +162,7 @@ export class Frontend {
75
162
  });
76
163
  }
77
164
  else {
165
+ // Load the SSL certificate, the private key and optionally the CA certificate
78
166
  let cert;
79
167
  try {
80
168
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -102,7 +190,9 @@ export class Frontend {
102
190
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
103
191
  }
104
192
  const serverOptions = { cert, key, ca };
193
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
105
194
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
195
+ // Listen on the specified port
106
196
  if (hasParameter('ingress')) {
107
197
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
108
198
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -116,6 +206,7 @@ export class Frontend {
116
206
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
117
207
  });
118
208
  }
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
210
  this.httpsServer.on('error', (error) => {
120
211
  this.log.error(`Frontend https server error listening on ${this.port}`);
121
212
  switch (error.code) {
@@ -132,16 +223,18 @@ export class Frontend {
132
223
  }
133
224
  if (this.initializeError)
134
225
  return;
226
+ // Create a WebSocket server and attach it to the http or https server
135
227
  const wssPort = this.port;
136
228
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
137
229
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
138
230
  this.webSocketServer.on('connection', (ws, request) => {
139
231
  const clientIp = request.socket.remoteAddress;
140
- let callbackLogLevel = "notice";
141
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
142
- callbackLogLevel = "info";
143
- if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
144
- callbackLogLevel = "debug";
232
+ // Set the global logger callback for the WebSocketServer
233
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
234
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
235
+ callbackLogLevel = "info" /* LogLevel.INFO */;
236
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
237
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
145
238
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
146
239
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
147
240
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -175,6 +268,7 @@ export class Frontend {
175
268
  this.webSocketServer.on('error', (ws, error) => {
176
269
  this.log.error(`WebSocketServer error: ${error}`);
177
270
  });
271
+ // Subscribe to cli events
178
272
  const { cliEmitter } = await import('./cli.js');
179
273
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
180
274
  this.wssSendUptimeUpdate(systemUptime, processUptime);
@@ -185,6 +279,7 @@ export class Frontend {
185
279
  cliEmitter.on('cpu', (cpuUsage) => {
186
280
  this.wssSendCpuUpdate(cpuUsage);
187
281
  });
282
+ // Endpoint to validate login code
188
283
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
189
284
  const { password } = req.body;
190
285
  this.log.debug('The frontend sent /api/login', password);
@@ -203,23 +298,27 @@ export class Frontend {
203
298
  this.log.warn('/api/login error wrong password');
204
299
  res.json({ valid: false });
205
300
  }
301
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
206
302
  }
207
303
  catch (error) {
208
304
  this.log.error('/api/login error getting password');
209
305
  res.json({ valid: false });
210
306
  }
211
307
  });
308
+ // Endpoint to provide health check
212
309
  this.expressApp.get('/health', (req, res) => {
213
310
  this.log.debug('Express received /health');
214
311
  const healthStatus = {
215
- status: 'ok',
216
- uptime: process.uptime(),
217
- timestamp: new Date().toISOString(),
312
+ status: 'ok', // Indicate service is healthy
313
+ uptime: process.uptime(), // Server uptime in seconds
314
+ timestamp: new Date().toISOString(), // Current timestamp
218
315
  };
219
316
  res.status(200).json(healthStatus);
220
317
  });
318
+ // Endpoint to provide memory usage details
221
319
  this.expressApp.get('/memory', async (req, res) => {
222
320
  this.log.debug('Express received /memory');
321
+ // Memory usage from process
223
322
  const memoryUsageRaw = process.memoryUsage();
224
323
  const memoryUsage = {
225
324
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -228,10 +327,13 @@ export class Frontend {
228
327
  external: this.formatMemoryUsage(memoryUsageRaw.external),
229
328
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
230
329
  };
330
+ // V8 heap statistics
231
331
  const { default: v8 } = await import('node:v8');
232
332
  const heapStatsRaw = v8.getHeapStatistics();
233
333
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
334
+ // Format heapStats
234
335
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
336
+ // Format heapSpaces
235
337
  const heapSpaces = heapSpacesRaw.map((space) => ({
236
338
  ...space,
237
339
  space_size: this.formatMemoryUsage(space.space_size),
@@ -249,6 +351,7 @@ export class Frontend {
249
351
  };
250
352
  res.status(200).json(memoryReport);
251
353
  });
354
+ // Endpoint to start advertising the server node
252
355
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
253
356
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
254
357
  if (pairingCodes) {
@@ -259,18 +362,22 @@ export class Frontend {
259
362
  res.status(500).json({ error: 'Failed to generate pairing codes' });
260
363
  }
261
364
  });
365
+ // Endpoint to provide settings
262
366
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
263
367
  this.log.debug('The frontend sent /api/settings');
264
368
  res.json(await this.getApiSettings());
265
369
  });
370
+ // Endpoint to provide plugins
266
371
  this.expressApp.get('/api/plugins', async (req, res) => {
267
372
  this.log.debug('The frontend sent /api/plugins');
268
373
  res.json(this.getBaseRegisteredPlugins());
269
374
  });
375
+ // Endpoint to provide devices
270
376
  this.expressApp.get('/api/devices', (req, res) => {
271
377
  this.log.debug('The frontend sent /api/devices');
272
378
  const devices = [];
273
379
  this.matterbridge.devices.forEach(async (device) => {
380
+ // Check if the device has the required properties
274
381
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
275
382
  return;
276
383
  const cluster = this.getClusterTextFromDevice(device);
@@ -288,6 +395,7 @@ export class Frontend {
288
395
  });
289
396
  res.json(devices);
290
397
  });
398
+ // Endpoint to provide the cluster servers of the devices
291
399
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
292
400
  const selectedPluginName = req.params.selectedPluginName;
293
401
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -360,6 +468,7 @@ export class Frontend {
360
468
  });
361
469
  res.json(data);
362
470
  });
471
+ // Endpoint to view the log
363
472
  this.expressApp.get('/api/view-log', async (req, res) => {
364
473
  this.log.debug('The frontend sent /api/log');
365
474
  try {
@@ -372,10 +481,12 @@ export class Frontend {
372
481
  res.status(500).send('Error reading log file');
373
482
  }
374
483
  });
484
+ // Endpoint to download the matterbridge log
375
485
  this.expressApp.get('/api/download-mblog', async (req, res) => {
376
486
  this.log.debug('The frontend sent /api/download-mblog');
377
487
  try {
378
488
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
489
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
379
490
  }
380
491
  catch (error) {
381
492
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -387,10 +498,12 @@ export class Frontend {
387
498
  }
388
499
  });
389
500
  });
501
+ // Endpoint to download the matter log
390
502
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
391
503
  this.log.debug('The frontend sent /api/download-mjlog');
392
504
  try {
393
505
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
506
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
394
507
  }
395
508
  catch (error) {
396
509
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -402,10 +515,12 @@ export class Frontend {
402
515
  }
403
516
  });
404
517
  });
518
+ // Endpoint to download the matter log
405
519
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
406
520
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
407
521
  try {
408
522
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
523
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
409
524
  }
410
525
  catch (error) {
411
526
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
@@ -417,6 +532,7 @@ export class Frontend {
417
532
  }
418
533
  });
419
534
  });
535
+ // Endpoint to download the matter storage file
420
536
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
421
537
  this.log.debug('The frontend sent /api/download-mjstorage');
422
538
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -427,6 +543,7 @@ export class Frontend {
427
543
  }
428
544
  });
429
545
  });
546
+ // Endpoint to download the matterbridge storage directory
430
547
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
431
548
  this.log.debug('The frontend sent /api/download-mbstorage');
432
549
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -437,6 +554,7 @@ export class Frontend {
437
554
  }
438
555
  });
439
556
  });
557
+ // Endpoint to download the matterbridge plugin directory
440
558
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
441
559
  this.log.debug('The frontend sent /api/download-pluginstorage');
442
560
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -447,9 +565,11 @@ export class Frontend {
447
565
  }
448
566
  });
449
567
  });
568
+ // Endpoint to download the matterbridge plugin config files
450
569
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
451
570
  this.log.debug('The frontend sent /api/download-pluginconfig');
452
571
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
572
+ // 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')));
453
573
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
454
574
  if (error) {
455
575
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -457,6 +577,7 @@ export class Frontend {
457
577
  }
458
578
  });
459
579
  });
580
+ // Endpoint to download the matterbridge plugin config files
460
581
  this.expressApp.get('/api/download-backup', async (req, res) => {
461
582
  this.log.debug('The frontend sent /api/download-backup');
462
583
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -466,6 +587,7 @@ export class Frontend {
466
587
  }
467
588
  });
468
589
  });
590
+ // Endpoint to receive commands
469
591
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
470
592
  const command = req.params.command;
471
593
  let param = req.params.param;
@@ -475,13 +597,15 @@ export class Frontend {
475
597
  return;
476
598
  }
477
599
  this.log.debug(`Received frontend command: ${command}:${param}`);
600
+ // Handle the command setpassword from Settings
478
601
  if (command === 'setpassword') {
479
- const password = param.slice(1, -1);
602
+ const password = param.slice(1, -1); // Remove the first and last characters
480
603
  this.log.debug('setpassword', param, password);
481
604
  await this.matterbridge.nodeContext?.set('password', password);
482
605
  res.json({ message: 'Command received' });
483
606
  return;
484
607
  }
608
+ // Handle the command setbridgemode from Settings
485
609
  if (command === 'setbridgemode') {
486
610
  this.log.debug(`setbridgemode: ${param}`);
487
611
  this.wssSendRestartRequired();
@@ -489,6 +613,7 @@ export class Frontend {
489
613
  res.json({ message: 'Command received' });
490
614
  return;
491
615
  }
616
+ // Handle the command backup from Settings
492
617
  if (command === 'backup') {
493
618
  this.log.notice(`Prepairing the backup...`);
494
619
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -497,31 +622,33 @@ export class Frontend {
497
622
  res.json({ message: 'Command received' });
498
623
  return;
499
624
  }
625
+ // Handle the command setmbloglevel from Settings
500
626
  if (command === 'setmbloglevel') {
501
627
  this.log.debug('Matterbridge log level:', param);
502
628
  if (param === 'Debug') {
503
- this.log.logLevel = "debug";
629
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
504
630
  }
505
631
  else if (param === 'Info') {
506
- this.log.logLevel = "info";
632
+ this.log.logLevel = "info" /* LogLevel.INFO */;
507
633
  }
508
634
  else if (param === 'Notice') {
509
- this.log.logLevel = "notice";
635
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
510
636
  }
511
637
  else if (param === 'Warn') {
512
- this.log.logLevel = "warn";
638
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
513
639
  }
514
640
  else if (param === 'Error') {
515
- this.log.logLevel = "error";
641
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
516
642
  }
517
643
  else if (param === 'Fatal') {
518
- this.log.logLevel = "fatal";
644
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
519
645
  }
520
646
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
521
647
  await this.matterbridge.setLogLevel(this.log.logLevel);
522
648
  res.json({ message: 'Command received' });
523
649
  return;
524
650
  }
651
+ // Handle the command setmbloglevel from Settings
525
652
  if (command === 'setmjloglevel') {
526
653
  this.log.debug('Matter.js log level:', param);
527
654
  if (param === 'Debug') {
@@ -546,30 +673,34 @@ export class Frontend {
546
673
  res.json({ message: 'Command received' });
547
674
  return;
548
675
  }
676
+ // Handle the command setmdnsinterface from Settings
549
677
  if (command === 'setmdnsinterface') {
550
- param = param.slice(1, -1);
678
+ param = param.slice(1, -1); // Remove the first and last characters *mdns*
551
679
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
552
680
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
553
681
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
554
682
  res.json({ message: 'Command received' });
555
683
  return;
556
684
  }
685
+ // Handle the command setipv4address from Settings
557
686
  if (command === 'setipv4address') {
558
- param = param.slice(1, -1);
687
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
559
688
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
560
689
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
561
690
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
562
691
  res.json({ message: 'Command received' });
563
692
  return;
564
693
  }
694
+ // Handle the command setipv6address from Settings
565
695
  if (command === 'setipv6address') {
566
- param = param.slice(1, -1);
696
+ param = param.slice(1, -1); // Remove the first and last characters *ip*
567
697
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
568
698
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
569
699
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
570
700
  res.json({ message: 'Command received' });
571
701
  return;
572
702
  }
703
+ // Handle the command setmatterport from Settings
573
704
  if (command === 'setmatterport') {
574
705
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
575
706
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -578,6 +709,7 @@ export class Frontend {
578
709
  res.json({ message: 'Command received' });
579
710
  return;
580
711
  }
712
+ // Handle the command setmatterdiscriminator from Settings
581
713
  if (command === 'setmatterdiscriminator') {
582
714
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
583
715
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -586,6 +718,7 @@ export class Frontend {
586
718
  res.json({ message: 'Command received' });
587
719
  return;
588
720
  }
721
+ // Handle the command setmatterpasscode from Settings
589
722
  if (command === 'setmatterpasscode') {
590
723
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
591
724
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -594,17 +727,20 @@ export class Frontend {
594
727
  res.json({ message: 'Command received' });
595
728
  return;
596
729
  }
730
+ // Handle the command setmbloglevel from Settings
597
731
  if (command === 'setmblogfile') {
598
732
  this.log.debug('Matterbridge file log:', param);
599
733
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
600
734
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
735
+ // Create the file logger for matterbridge
601
736
  if (param === 'true')
602
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
737
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
603
738
  else
604
739
  AnsiLogger.setGlobalLogfile(undefined);
605
740
  res.json({ message: 'Command received' });
606
741
  return;
607
742
  }
743
+ // Handle the command setmbloglevel from Settings
608
744
  if (command === 'setmjlogfile') {
609
745
  this.log.debug('Matter file log:', param);
610
746
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -631,40 +767,48 @@ export class Frontend {
631
767
  res.json({ message: 'Command received' });
632
768
  return;
633
769
  }
770
+ // Handle the command unregister from Settings
634
771
  if (command === 'unregister') {
635
772
  await this.matterbridge.unregisterAndShutdownProcess();
636
773
  res.json({ message: 'Command received' });
637
774
  return;
638
775
  }
776
+ // Handle the command reset from Settings
639
777
  if (command === 'reset') {
640
778
  await this.matterbridge.shutdownProcessAndReset();
641
779
  res.json({ message: 'Command received' });
642
780
  return;
643
781
  }
782
+ // Handle the command factoryreset from Settings
644
783
  if (command === 'factoryreset') {
645
784
  await this.matterbridge.shutdownProcessAndFactoryReset();
646
785
  res.json({ message: 'Command received' });
647
786
  return;
648
787
  }
788
+ // Handle the command shutdown from Header
649
789
  if (command === 'shutdown') {
650
790
  await this.matterbridge.shutdownProcess();
651
791
  res.json({ message: 'Command received' });
652
792
  return;
653
793
  }
794
+ // Handle the command restart from Header
654
795
  if (command === 'restart') {
655
796
  await this.matterbridge.restartProcess();
656
797
  res.json({ message: 'Command received' });
657
798
  return;
658
799
  }
800
+ // Handle the command update from Header
659
801
  if (command === 'update') {
660
802
  await this.matterbridge.updateProcess();
661
803
  this.wssSendRestartRequired();
662
804
  res.json({ message: 'Command received' });
663
805
  return;
664
806
  }
807
+ // Handle the command saveconfig from Home
665
808
  if (command === 'saveconfig') {
666
809
  param = param.replace(/\*/g, '\\');
667
810
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
811
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
668
812
  if (!this.matterbridge.plugins.has(param)) {
669
813
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
670
814
  }
@@ -679,6 +823,7 @@ export class Frontend {
679
823
  res.json({ message: 'Command received' });
680
824
  return;
681
825
  }
826
+ // Handle the command installplugin from Home
682
827
  if (command === 'installplugin') {
683
828
  param = param.replace(/\*/g, '\\');
684
829
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
@@ -687,6 +832,7 @@ export class Frontend {
687
832
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
688
833
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
689
834
  this.wssSendSnackbarMessage(`Installed package ${param}`);
835
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
690
836
  }
691
837
  catch (error) {
692
838
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
@@ -695,16 +841,21 @@ export class Frontend {
695
841
  this.wssSendSnackbarMessage(`Restart required`, 0);
696
842
  this.wssSendRestartRequired();
697
843
  param = param.split('@')[0];
844
+ // Also add the plugin to matterbridge so no return!
698
845
  if (param === 'matterbridge') {
846
+ // 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
699
847
  res.json({ message: 'Command received' });
700
848
  return;
701
849
  }
702
850
  }
851
+ // Handle the command addplugin from Home
703
852
  if (command === 'addplugin' || command === 'installplugin') {
704
853
  param = param.replace(/\*/g, '\\');
705
854
  const plugin = await this.matterbridge.plugins.add(param);
706
855
  if (plugin) {
707
856
  if (this.matterbridge.bridgeMode === 'childbridge') {
857
+ // 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
858
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
708
859
  this.matterbridge.createDynamicPlugin(plugin, true);
709
860
  }
710
861
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
@@ -714,19 +865,21 @@ export class Frontend {
714
865
  res.json({ message: 'Command received' });
715
866
  return;
716
867
  }
868
+ // Handle the command removeplugin from Home
717
869
  if (command === 'removeplugin') {
718
870
  if (!this.matterbridge.plugins.has(param)) {
719
871
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
720
872
  }
721
873
  else {
722
874
  const plugin = this.matterbridge.plugins.get(param);
723
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
875
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
724
876
  await this.matterbridge.plugins.remove(param);
725
877
  }
726
878
  res.json({ message: 'Command received' });
727
879
  this.wssSendRefreshRequired();
728
880
  return;
729
881
  }
882
+ // Handle the command enableplugin from Home
730
883
  if (command === 'enableplugin') {
731
884
  if (!this.matterbridge.plugins.has(param)) {
732
885
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -744,6 +897,7 @@ export class Frontend {
744
897
  plugin.addedDevices = undefined;
745
898
  await this.matterbridge.plugins.enable(param);
746
899
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
900
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
747
901
  this.matterbridge.createDynamicPlugin(plugin, true);
748
902
  }
749
903
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
@@ -755,6 +909,7 @@ export class Frontend {
755
909
  this.wssSendRefreshRequired();
756
910
  return;
757
911
  }
912
+ // Handle the command disableplugin from Home
758
913
  if (command === 'disableplugin') {
759
914
  if (!this.matterbridge.plugins.has(param)) {
760
915
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -762,7 +917,7 @@ export class Frontend {
762
917
  else {
763
918
  const plugin = this.matterbridge.plugins.get(param);
764
919
  if (plugin && plugin.enabled) {
765
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
920
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
766
921
  await this.matterbridge.plugins.disable(param);
767
922
  }
768
923
  }
@@ -771,6 +926,7 @@ export class Frontend {
771
926
  return;
772
927
  }
773
928
  });
929
+ // Fallback for routing (must be the last route)
774
930
  this.expressApp.get('*', (req, res) => {
775
931
  this.log.debug('The frontend sent:', req.url);
776
932
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -779,24 +935,29 @@ export class Frontend {
779
935
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
780
936
  }
781
937
  async stop() {
938
+ // Close the http server
782
939
  if (this.httpServer) {
783
940
  this.httpServer.close();
784
941
  this.httpServer.removeAllListeners();
785
942
  this.httpServer = undefined;
786
943
  this.log.debug('Frontend http server closed successfully');
787
944
  }
945
+ // Close the https server
788
946
  if (this.httpsServer) {
789
947
  this.httpsServer.close();
790
948
  this.httpsServer.removeAllListeners();
791
949
  this.httpsServer = undefined;
792
950
  this.log.debug('Frontend https server closed successfully');
793
951
  }
952
+ // Remove listeners from the express app
794
953
  if (this.expressApp) {
795
954
  this.expressApp.removeAllListeners();
796
955
  this.expressApp = undefined;
797
956
  this.log.debug('Frontend app closed successfully');
798
957
  }
958
+ // Close the WebSocket server
799
959
  if (this.webSocketServer) {
960
+ // Close all active connections
800
961
  this.webSocketServer.clients.forEach((client) => {
801
962
  if (client.readyState === WebSocket.OPEN) {
802
963
  client.close();
@@ -813,6 +974,7 @@ export class Frontend {
813
974
  this.webSocketServer = undefined;
814
975
  }
815
976
  }
977
+ // Function to format bytes to KB, MB, or GB
816
978
  formatMemoryUsage = (bytes) => {
817
979
  if (bytes >= 1024 ** 3) {
818
980
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -824,6 +986,7 @@ export class Frontend {
824
986
  return `${(bytes / 1024).toFixed(2)} KB`;
825
987
  }
826
988
  };
989
+ // Function to format system uptime with only the most significant unit
827
990
  formatOsUpTime = (seconds) => {
828
991
  if (seconds >= 86400) {
829
992
  const days = Math.floor(seconds / 86400);
@@ -839,8 +1002,13 @@ export class Frontend {
839
1002
  }
840
1003
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
841
1004
  };
1005
+ /**
1006
+ * Retrieves the api settings data.
1007
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1008
+ */
842
1009
  async getApiSettings() {
843
1010
  const { lastCpuUsage } = await import('./cli.js');
1011
+ // Update the system information
844
1012
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
845
1013
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
846
1014
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -849,6 +1017,7 @@ export class Frontend {
849
1017
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
850
1018
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
851
1019
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1020
+ // Update the matterbridge information
852
1021
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
853
1022
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
854
1023
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -867,7 +1036,14 @@ export class Frontend {
867
1036
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
868
1037
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
869
1038
  }
1039
+ /**
1040
+ * Retrieves the cluster text description from a given device.
1041
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1042
+ * @returns {string} The attributes description of the cluster servers in the device.
1043
+ */
870
1044
  getClusterTextFromDevice(device) {
1045
+ if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1046
+ return '';
871
1047
  const getAttribute = (device, cluster, attribute) => {
872
1048
  let value = undefined;
873
1049
  Object.entries(device.state)
@@ -905,6 +1081,7 @@ export class Frontend {
905
1081
  };
906
1082
  let attributes = '';
907
1083
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1084
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
908
1085
  if (typeof attributeValue === 'undefined')
909
1086
  return;
910
1087
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -982,8 +1159,13 @@ export class Frontend {
982
1159
  if (clusterName === 'userLabel' && attributeName === 'labelList')
983
1160
  attributes += `${getUserLabel(device)} `;
984
1161
  });
1162
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
985
1163
  return attributes.trimStart().trimEnd();
986
1164
  }
1165
+ /**
1166
+ * Retrieves the base registered plugins sanitized for res.json().
1167
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1168
+ */
987
1169
  getBaseRegisteredPlugins() {
988
1170
  const baseRegisteredPlugins = [];
989
1171
  for (const plugin of this.matterbridge.plugins) {
@@ -1014,6 +1196,13 @@ export class Frontend {
1014
1196
  }
1015
1197
  return baseRegisteredPlugins;
1016
1198
  }
1199
+ /**
1200
+ * Handles incoming websocket messages for the Matterbridge frontend.
1201
+ *
1202
+ * @param {WebSocket} client - The websocket client that sent the message.
1203
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1204
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1205
+ */
1017
1206
  async wsMessageHandler(client, message) {
1018
1207
  let data;
1019
1208
  try {
@@ -1159,8 +1348,10 @@ export class Frontend {
1159
1348
  else if (data.method === '/api/devices') {
1160
1349
  const devices = [];
1161
1350
  this.matterbridge.devices.forEach(async (device) => {
1351
+ // Filter by pluginName if provided
1162
1352
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1163
1353
  return;
1354
+ // Check if the device has the required properties
1164
1355
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1165
1356
  return;
1166
1357
  const cluster = this.getClusterTextFromDevice(device);
@@ -1243,6 +1434,7 @@ export class Frontend {
1243
1434
  });
1244
1435
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1245
1436
  deviceTypes = [];
1437
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1246
1438
  const name = childEndpoint.endpoint?.id;
1247
1439
  const clusterServers = childEndpoint.getAllClusterServers();
1248
1440
  clusterServers.forEach((clusterServer) => {
@@ -1325,85 +1517,139 @@ export class Frontend {
1325
1517
  return;
1326
1518
  }
1327
1519
  }
1520
+ /**
1521
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1522
+ *
1523
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1524
+ * @param {string} time - The time string of the message
1525
+ * @param {string} name - The logger name of the message
1526
+ * @param {string} message - The content of the message.
1527
+ */
1328
1528
  wssSendMessage(level, time, name, message) {
1329
1529
  if (!level || !time || !name || !message)
1330
1530
  return;
1531
+ // Remove ANSI escape codes from the message
1532
+ // eslint-disable-next-line no-control-regex
1331
1533
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1534
+ // Remove leading asterisks from the message
1332
1535
  message = message.replace(/^\*+/, '');
1536
+ // Replace all occurrences of \t and \n
1333
1537
  message = message.replace(/[\t\n]/g, '');
1538
+ // Remove non-printable characters
1539
+ // eslint-disable-next-line no-control-regex
1334
1540
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1541
+ // Replace all occurrences of \" with "
1335
1542
  message = message.replace(/\\"/g, '"');
1543
+ // Define the maximum allowed length for continuous characters without a space
1336
1544
  const maxContinuousLength = 100;
1337
1545
  const keepStartLength = 20;
1338
1546
  const keepEndLength = 20;
1547
+ // Split the message into words
1339
1548
  message = message
1340
1549
  .split(' ')
1341
1550
  .map((word) => {
1551
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1342
1552
  if (word.length > maxContinuousLength) {
1343
1553
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1344
1554
  }
1345
1555
  return word;
1346
1556
  })
1347
1557
  .join(' ');
1558
+ // Send the message to all connected clients
1348
1559
  this.webSocketServer?.clients.forEach((client) => {
1349
1560
  if (client.readyState === WebSocket.OPEN) {
1350
1561
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1351
1562
  }
1352
1563
  });
1353
1564
  }
1565
+ /**
1566
+ * Sends a need to refresh WebSocket message to all connected clients.
1567
+ *
1568
+ */
1354
1569
  wssSendRefreshRequired() {
1355
1570
  this.log.debug('Sending a refresh required message to all connected clients');
1356
1571
  this.matterbridge.matterbridgeInformation.refreshRequired = true;
1572
+ // Send the message to all connected clients
1357
1573
  this.webSocketServer?.clients.forEach((client) => {
1358
1574
  if (client.readyState === WebSocket.OPEN) {
1359
1575
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1360
1576
  }
1361
1577
  });
1362
1578
  }
1579
+ /**
1580
+ * Sends a need to restart WebSocket message to all connected clients.
1581
+ *
1582
+ */
1363
1583
  wssSendRestartRequired() {
1364
1584
  this.log.debug('Sending a restart required message to all connected clients');
1365
1585
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1366
1586
  this.wssSendSnackbarMessage(`Restart required`, 0);
1587
+ // Send the message to all connected clients
1367
1588
  this.webSocketServer?.clients.forEach((client) => {
1368
1589
  if (client.readyState === WebSocket.OPEN) {
1369
1590
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1370
1591
  }
1371
1592
  });
1372
1593
  }
1594
+ /**
1595
+ * Sends a memory update message to all connected clients.
1596
+ *
1597
+ */
1373
1598
  wssSendCpuUpdate(cpuUsage) {
1374
1599
  this.log.debug('Sending a cpu update message to all connected clients');
1600
+ // Send the message to all connected clients
1375
1601
  this.webSocketServer?.clients.forEach((client) => {
1376
1602
  if (client.readyState === WebSocket.OPEN) {
1377
1603
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1378
1604
  }
1379
1605
  });
1380
1606
  }
1607
+ /**
1608
+ * Sends a cpu update message to all connected clients.
1609
+ *
1610
+ */
1381
1611
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1382
1612
  this.log.debug('Sending a memory update message to all connected clients');
1613
+ // Send the message to all connected clients
1383
1614
  this.webSocketServer?.clients.forEach((client) => {
1384
1615
  if (client.readyState === WebSocket.OPEN) {
1385
1616
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1386
1617
  }
1387
1618
  });
1388
1619
  }
1620
+ /**
1621
+ * Sends a memory update message to all connected clients.
1622
+ *
1623
+ */
1389
1624
  wssSendUptimeUpdate(systemUptime, processUptime) {
1390
1625
  this.log.debug('Sending a uptime update message to all connected clients');
1626
+ // Send the message to all connected clients
1391
1627
  this.webSocketServer?.clients.forEach((client) => {
1392
1628
  if (client.readyState === WebSocket.OPEN) {
1393
1629
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1394
1630
  }
1395
1631
  });
1396
1632
  }
1633
+ /**
1634
+ * Sends a cpu update message to all connected clients.
1635
+ *
1636
+ */
1397
1637
  wssSendSnackbarMessage(message, timeout = 5) {
1398
1638
  this.log.debug('Sending a snackbar message to all connected clients');
1639
+ // Send the message to all connected clients
1399
1640
  this.webSocketServer?.clients.forEach((client) => {
1400
1641
  if (client.readyState === WebSocket.OPEN) {
1401
1642
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
1402
1643
  }
1403
1644
  });
1404
1645
  }
1646
+ /**
1647
+ * Sends a message to all connected clients.
1648
+ *
1649
+ */
1405
1650
  wssBroadcastMessage(id, method, params) {
1406
1651
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1652
+ // Send the message to all connected clients
1407
1653
  this.webSocketServer?.clients.forEach((client) => {
1408
1654
  if (client.readyState === WebSocket.OPEN) {
1409
1655
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1411,3 +1657,4 @@ export class Frontend {
1411
1657
  });
1412
1658
  }
1413
1659
  }
1660
+ //# sourceMappingURL=frontend.js.map