matterbridge 2.2.6-dev.5 → 2.2.6

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 +2 -1
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +37 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -2
  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 +325 -19
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -1
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +5 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +425 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +753 -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 +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 +285 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +216 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +179 -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 +92 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +146 -6
  97. package/dist/shelly.js.map +1 -0
  98. package/dist/storage/export.d.ts +2 -0
  99. package/dist/storage/export.d.ts.map +1 -0
  100. package/dist/storage/export.js +1 -0
  101. package/dist/storage/export.js.map +1 -0
  102. package/dist/update.d.ts +32 -0
  103. package/dist/update.d.ts.map +1 -0
  104. package/dist/update.js +45 -0
  105. package/dist/update.js.map +1 -0
  106. package/dist/utils/colorUtils.d.ts +61 -0
  107. package/dist/utils/colorUtils.d.ts.map +1 -0
  108. package/dist/utils/colorUtils.js +205 -2
  109. package/dist/utils/colorUtils.js.map +1 -0
  110. package/dist/utils/copyDirectory.d.ts +32 -0
  111. package/dist/utils/copyDirectory.d.ts.map +1 -0
  112. package/dist/utils/copyDirectory.js +37 -1
  113. package/dist/utils/copyDirectory.js.map +1 -0
  114. package/dist/utils/createZip.d.ts +38 -0
  115. package/dist/utils/createZip.d.ts.map +1 -0
  116. package/dist/utils/createZip.js +42 -2
  117. package/dist/utils/createZip.js.map +1 -0
  118. package/dist/utils/deepCopy.d.ts +31 -0
  119. package/dist/utils/deepCopy.d.ts.map +1 -0
  120. package/dist/utils/deepCopy.js +40 -0
  121. package/dist/utils/deepCopy.js.map +1 -0
  122. package/dist/utils/deepEqual.d.ts +53 -0
  123. package/dist/utils/deepEqual.d.ts.map +1 -0
  124. package/dist/utils/deepEqual.js +65 -1
  125. package/dist/utils/deepEqual.js.map +1 -0
  126. package/dist/utils/export.d.ts +10 -0
  127. package/dist/utils/export.d.ts.map +1 -0
  128. package/dist/utils/export.js +1 -0
  129. package/dist/utils/export.js.map +1 -0
  130. package/dist/utils/isvalid.d.ts +87 -0
  131. package/dist/utils/isvalid.d.ts.map +1 -0
  132. package/dist/utils/isvalid.js +86 -0
  133. package/dist/utils/isvalid.js.map +1 -0
  134. package/dist/utils/network.d.ts +69 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +77 -8
  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
+ /**
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,6 +686,7 @@ 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
691
  if (param === 'json' && isValidString(req.body.value)) {
560
692
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = req.body.value;
@@ -564,6 +696,7 @@ export class Frontend {
564
696
  res.json({ message: 'Command received' });
565
697
  return;
566
698
  }
699
+ // Handle the command setipv4address from Settings
567
700
  if (command === 'setipv4address') {
568
701
  if (param === 'json' && isValidString(req.body.value)) {
569
702
  this.log.debug(`Matter.js ipv4 address: ${req.body.value === '' ? 'all ipv4 addresses' : req.body.value}`);
@@ -573,6 +706,7 @@ export class Frontend {
573
706
  res.json({ message: 'Command received' });
574
707
  return;
575
708
  }
709
+ // Handle the command setipv6address from Settings
576
710
  if (command === 'setipv6address') {
577
711
  if (param === 'json' && isValidString(req.body.value)) {
578
712
  this.log.debug(`Matter.js ipv6 address: ${req.body.value === '' ? 'all ipv6 addresses' : req.body.value}`);
@@ -582,6 +716,7 @@ export class Frontend {
582
716
  res.json({ message: 'Command received' });
583
717
  return;
584
718
  }
719
+ // Handle the command setmatterport from Settings
585
720
  if (command === 'setmatterport') {
586
721
  const port = Math.min(Math.max(parseInt(req.body.value), 5540), 5560);
587
722
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -590,6 +725,7 @@ export class Frontend {
590
725
  res.json({ message: 'Command received' });
591
726
  return;
592
727
  }
728
+ // Handle the command setmatterdiscriminator from Settings
593
729
  if (command === 'setmatterdiscriminator') {
594
730
  const discriminator = Math.min(Math.max(parseInt(req.body.value), 1000), 4095);
595
731
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -598,6 +734,7 @@ export class Frontend {
598
734
  res.json({ message: 'Command received' });
599
735
  return;
600
736
  }
737
+ // Handle the command setmatterpasscode from Settings
601
738
  if (command === 'setmatterpasscode') {
602
739
  const passcode = Math.min(Math.max(parseInt(req.body.value), 10000000), 90000000);
603
740
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -606,17 +743,20 @@ export class Frontend {
606
743
  res.json({ message: 'Command received' });
607
744
  return;
608
745
  }
746
+ // Handle the command setmbloglevel from Settings
609
747
  if (command === 'setmblogfile') {
610
748
  this.log.debug('Matterbridge file log:', param);
611
749
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
612
750
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
751
+ // Create the file logger for matterbridge
613
752
  if (param === 'true')
614
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
753
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
615
754
  else
616
755
  AnsiLogger.setGlobalLogfile(undefined);
617
756
  res.json({ message: 'Command received' });
618
757
  return;
619
758
  }
759
+ // Handle the command setmbloglevel from Settings
620
760
  if (command === 'setmjlogfile') {
621
761
  this.log.debug('Matter file log:', param);
622
762
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -643,40 +783,48 @@ export class Frontend {
643
783
  res.json({ message: 'Command received' });
644
784
  return;
645
785
  }
786
+ // Handle the command unregister from Settings
646
787
  if (command === 'unregister') {
647
788
  await this.matterbridge.unregisterAndShutdownProcess();
648
789
  res.json({ message: 'Command received' });
649
790
  return;
650
791
  }
792
+ // Handle the command reset from Settings
651
793
  if (command === 'reset') {
652
794
  await this.matterbridge.shutdownProcessAndReset();
653
795
  res.json({ message: 'Command received' });
654
796
  return;
655
797
  }
798
+ // Handle the command factoryreset from Settings
656
799
  if (command === 'factoryreset') {
657
800
  await this.matterbridge.shutdownProcessAndFactoryReset();
658
801
  res.json({ message: 'Command received' });
659
802
  return;
660
803
  }
804
+ // Handle the command shutdown from Header
661
805
  if (command === 'shutdown') {
662
806
  await this.matterbridge.shutdownProcess();
663
807
  res.json({ message: 'Command received' });
664
808
  return;
665
809
  }
810
+ // Handle the command restart from Header
666
811
  if (command === 'restart') {
667
812
  await this.matterbridge.restartProcess();
668
813
  res.json({ message: 'Command received' });
669
814
  return;
670
815
  }
816
+ // Handle the command update from Header
671
817
  if (command === 'update') {
672
818
  await this.matterbridge.updateProcess();
673
819
  this.wssSendRestartRequired();
674
820
  res.json({ message: 'Command received' });
675
821
  return;
676
822
  }
823
+ // Handle the command saveconfig from Home
677
824
  if (command === 'saveconfig') {
678
825
  param = param.replace(/\*/g, '\\');
679
826
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
827
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
680
828
  if (!this.matterbridge.plugins.has(param)) {
681
829
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
682
830
  }
@@ -691,6 +839,7 @@ export class Frontend {
691
839
  res.json({ message: 'Command received' });
692
840
  return;
693
841
  }
842
+ // Handle the command installplugin from Home
694
843
  if (command === 'installplugin') {
695
844
  param = param.replace(/\*/g, '\\');
696
845
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
@@ -699,6 +848,7 @@ export class Frontend {
699
848
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
700
849
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
701
850
  this.wssSendSnackbarMessage(`Installed package ${param}`, 10, 'success');
851
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
702
852
  }
703
853
  catch (error) {
704
854
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
@@ -706,17 +856,22 @@ export class Frontend {
706
856
  }
707
857
  this.wssSendRestartRequired();
708
858
  param = param.split('@')[0];
859
+ // Also add the plugin to matterbridge so no return!
709
860
  if (param === 'matterbridge') {
861
+ // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
710
862
  res.json({ message: 'Command received' });
711
863
  return;
712
864
  }
713
865
  }
866
+ // Handle the command addplugin from Home
714
867
  if (command === 'addplugin' || command === 'installplugin') {
715
868
  param = param.replace(/\*/g, '\\');
716
869
  const plugin = await this.matterbridge.plugins.add(param);
717
870
  if (plugin) {
718
871
  this.wssSendSnackbarMessage(`Added plugin ${param}`);
719
872
  if (this.matterbridge.bridgeMode === 'childbridge') {
873
+ // 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
874
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
720
875
  this.matterbridge.createDynamicPlugin(plugin, true);
721
876
  }
722
877
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
@@ -726,13 +881,14 @@ export class Frontend {
726
881
  res.json({ message: 'Command received' });
727
882
  return;
728
883
  }
884
+ // Handle the command removeplugin from Home
729
885
  if (command === 'removeplugin') {
730
886
  if (!this.matterbridge.plugins.has(param)) {
731
887
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
732
888
  }
733
889
  else {
734
890
  const plugin = this.matterbridge.plugins.get(param);
735
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
891
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
736
892
  await this.matterbridge.plugins.remove(param);
737
893
  this.wssSendSnackbarMessage(`Removed plugin ${param}`);
738
894
  this.wssSendRefreshRequired('plugins');
@@ -740,6 +896,7 @@ export class Frontend {
740
896
  res.json({ message: 'Command received' });
741
897
  return;
742
898
  }
899
+ // Handle the command enableplugin from Home
743
900
  if (command === 'enableplugin') {
744
901
  if (!this.matterbridge.plugins.has(param)) {
745
902
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -758,6 +915,7 @@ export class Frontend {
758
915
  await this.matterbridge.plugins.enable(param);
759
916
  this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
760
917
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
918
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
761
919
  this.matterbridge.createDynamicPlugin(plugin, true);
762
920
  }
763
921
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
@@ -768,6 +926,7 @@ export class Frontend {
768
926
  res.json({ message: 'Command received' });
769
927
  return;
770
928
  }
929
+ // Handle the command disableplugin from Home
771
930
  if (command === 'disableplugin') {
772
931
  if (!this.matterbridge.plugins.has(param)) {
773
932
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -775,7 +934,7 @@ export class Frontend {
775
934
  else {
776
935
  const plugin = this.matterbridge.plugins.get(param);
777
936
  if (plugin && plugin.enabled) {
778
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
937
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
779
938
  await this.matterbridge.plugins.disable(param);
780
939
  this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
781
940
  this.wssSendRefreshRequired('plugins');
@@ -794,10 +953,13 @@ export class Frontend {
794
953
  return;
795
954
  }
796
955
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`);
956
+ // Define the path where the plugin file will be saved
797
957
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
798
958
  try {
959
+ // Move the uploaded file to the specified path
799
960
  await fs.rename(file.path, filePath);
800
961
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
962
+ // Install the plugin package
801
963
  if (filename.endsWith('.tgz')) {
802
964
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
803
965
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -814,6 +976,7 @@ export class Frontend {
814
976
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
815
977
  }
816
978
  });
979
+ // Fallback for routing (must be the last route)
817
980
  this.expressApp.get('*', (req, res) => {
818
981
  this.log.debug('The frontend sent:', req.url);
819
982
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -822,24 +985,31 @@ export class Frontend {
822
985
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
823
986
  }
824
987
  async stop() {
988
+ // Remove all listeners from the cliEmitter
989
+ // cliEmitter.removeAllListeners();
990
+ // Close the http server
825
991
  if (this.httpServer) {
826
992
  this.httpServer.close();
827
993
  this.httpServer.removeAllListeners();
828
994
  this.httpServer = undefined;
829
995
  this.log.debug('Frontend http server closed successfully');
830
996
  }
997
+ // Close the https server
831
998
  if (this.httpsServer) {
832
999
  this.httpsServer.close();
833
1000
  this.httpsServer.removeAllListeners();
834
1001
  this.httpsServer = undefined;
835
1002
  this.log.debug('Frontend https server closed successfully');
836
1003
  }
1004
+ // Remove listeners from the express app
837
1005
  if (this.expressApp) {
838
1006
  this.expressApp.removeAllListeners();
839
1007
  this.expressApp = undefined;
840
1008
  this.log.debug('Frontend app closed successfully');
841
1009
  }
1010
+ // Close the WebSocket server
842
1011
  if (this.webSocketServer) {
1012
+ // Close all active connections
843
1013
  this.webSocketServer.clients.forEach((client) => {
844
1014
  if (client.readyState === WebSocket.OPEN) {
845
1015
  client.close();
@@ -856,6 +1026,7 @@ export class Frontend {
856
1026
  this.webSocketServer = undefined;
857
1027
  }
858
1028
  }
1029
+ // Function to format bytes to KB, MB, or GB
859
1030
  formatMemoryUsage = (bytes) => {
860
1031
  if (bytes >= 1024 ** 3) {
861
1032
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -867,6 +1038,7 @@ export class Frontend {
867
1038
  return `${(bytes / 1024).toFixed(2)} KB`;
868
1039
  }
869
1040
  };
1041
+ // Function to format system uptime with only the most significant unit
870
1042
  formatOsUpTime = (seconds) => {
871
1043
  if (seconds >= 86400) {
872
1044
  const days = Math.floor(seconds / 86400);
@@ -882,8 +1054,13 @@ export class Frontend {
882
1054
  }
883
1055
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
884
1056
  };
1057
+ /**
1058
+ * Retrieves the api settings data.
1059
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1060
+ */
885
1061
  async getApiSettings() {
886
1062
  const { lastCpuUsage } = await import('./cli.js');
1063
+ // Update the system information
887
1064
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
888
1065
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
889
1066
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -892,6 +1069,7 @@ export class Frontend {
892
1069
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
893
1070
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
894
1071
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1072
+ // Update the matterbridge information
895
1073
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
896
1074
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
897
1075
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -910,6 +1088,11 @@ export class Frontend {
910
1088
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
911
1089
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
912
1090
  }
1091
+ /**
1092
+ * Retrieves the reachable attribute.
1093
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1094
+ * @returns {boolean} The reachable attribute.
1095
+ */
913
1096
  getReachability(device) {
914
1097
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
915
1098
  return false;
@@ -919,6 +1102,11 @@ export class Frontend {
919
1102
  return true;
920
1103
  return false;
921
1104
  }
1105
+ /**
1106
+ * Retrieves the cluster text description from a given device.
1107
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1108
+ * @returns {string} The attributes description of the cluster servers in the device.
1109
+ */
922
1110
  getClusterTextFromDevice(device) {
923
1111
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
924
1112
  return '';
@@ -959,6 +1147,7 @@ export class Frontend {
959
1147
  };
960
1148
  let attributes = '';
961
1149
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1150
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
962
1151
  if (typeof attributeValue === 'undefined')
963
1152
  return;
964
1153
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -1036,8 +1225,13 @@ export class Frontend {
1036
1225
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1037
1226
  attributes += `${getUserLabel(device)} `;
1038
1227
  });
1228
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1039
1229
  return attributes.trimStart().trimEnd();
1040
1230
  }
1231
+ /**
1232
+ * Retrieves the base registered plugins sanitized for res.json().
1233
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1234
+ */
1041
1235
  getBaseRegisteredPlugins() {
1042
1236
  const baseRegisteredPlugins = [];
1043
1237
  for (const plugin of this.matterbridge.plugins) {
@@ -1070,6 +1264,13 @@ export class Frontend {
1070
1264
  }
1071
1265
  return baseRegisteredPlugins;
1072
1266
  }
1267
+ /**
1268
+ * Handles incoming websocket messages for the Matterbridge frontend.
1269
+ *
1270
+ * @param {WebSocket} client - The websocket client that sent the message.
1271
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1272
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1273
+ */
1073
1274
  async wsMessageHandler(client, message) {
1074
1275
  let data;
1075
1276
  try {
@@ -1225,8 +1426,10 @@ export class Frontend {
1225
1426
  else if (data.method === '/api/devices') {
1226
1427
  const devices = [];
1227
1428
  this.matterbridge.devices.forEach(async (device) => {
1429
+ // Filter by pluginName if provided
1228
1430
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1229
1431
  return;
1432
+ // Check if the device has the required properties
1230
1433
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1231
1434
  return;
1232
1435
  const cluster = this.getClusterTextFromDevice(device);
@@ -1310,6 +1513,7 @@ export class Frontend {
1310
1513
  });
1311
1514
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1312
1515
  deviceTypes = [];
1516
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1313
1517
  const name = childEndpoint.endpoint?.id;
1314
1518
  const clusterServers = childEndpoint.getAllClusterServers();
1315
1519
  clusterServers.forEach((clusterServer) => {
@@ -1363,6 +1567,7 @@ export class Frontend {
1363
1567
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select' }));
1364
1568
  return;
1365
1569
  }
1570
+ // const selectDeviceValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectDevice.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1366
1571
  const selectDeviceValues = plugin.platform?.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1367
1572
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectDeviceValues }));
1368
1573
  return;
@@ -1377,6 +1582,7 @@ export class Frontend {
1377
1582
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' }));
1378
1583
  return;
1379
1584
  }
1585
+ // const selectEntityValues = plugin.platform?.selectDevice ? Array.from(plugin.platform.selectEntity.values()).sort((keyA, keyB) => keyA.name.localeCompare(keyB.name)) : [];
1380
1586
  const selectEntityValues = plugin.platform?.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1381
1587
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectEntityValues }));
1382
1588
  return;
@@ -1408,6 +1614,7 @@ export class Frontend {
1408
1614
  return;
1409
1615
  }
1410
1616
  const config = plugin.configJson;
1617
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1411
1618
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1412
1619
  this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1413
1620
  if (select === 'serial')
@@ -1415,9 +1622,11 @@ export class Frontend {
1415
1622
  if (select === 'name')
1416
1623
  this.log.info(`Selected device name ${data.params.name}`);
1417
1624
  if (config && select && (select === 'serial' || select === 'name')) {
1625
+ // Remove postfix from the serial if it exists
1418
1626
  if (config.postfix) {
1419
1627
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1420
1628
  }
1629
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1421
1630
  if (isValidArray(config.whiteList, 1)) {
1422
1631
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1423
1632
  config.whiteList.push(data.params.serial);
@@ -1426,6 +1635,7 @@ export class Frontend {
1426
1635
  config.whiteList.push(data.params.name);
1427
1636
  }
1428
1637
  }
1638
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1429
1639
  if (isValidArray(config.blackList, 1)) {
1430
1640
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1431
1641
  config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
@@ -1447,6 +1657,7 @@ export class Frontend {
1447
1657
  return;
1448
1658
  }
1449
1659
  const config = plugin.configJson;
1660
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1450
1661
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1451
1662
  this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1452
1663
  if (select === 'serial')
@@ -1457,6 +1668,7 @@ export class Frontend {
1457
1668
  if (config.postfix) {
1458
1669
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1459
1670
  }
1671
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1460
1672
  if (isValidArray(config.whiteList, 1)) {
1461
1673
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1462
1674
  config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
@@ -1465,6 +1677,7 @@ export class Frontend {
1465
1677
  config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
1466
1678
  }
1467
1679
  }
1680
+ // Add the serial to the blackList
1468
1681
  if (isValidArray(config.blackList)) {
1469
1682
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1470
1683
  config.blackList.push(data.params.serial);
@@ -1491,102 +1704,194 @@ export class Frontend {
1491
1704
  return;
1492
1705
  }
1493
1706
  }
1707
+ /**
1708
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1709
+ *
1710
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1711
+ * @param {string} time - The time string of the message
1712
+ * @param {string} name - The logger name of the message
1713
+ * @param {string} message - The content of the message.
1714
+ */
1494
1715
  wssSendMessage(level, time, name, message) {
1495
1716
  if (!level || !time || !name || !message)
1496
1717
  return;
1718
+ // Remove ANSI escape codes from the message
1719
+ // eslint-disable-next-line no-control-regex
1497
1720
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1721
+ // Remove leading asterisks from the message
1498
1722
  message = message.replace(/^\*+/, '');
1723
+ // Replace all occurrences of \t and \n
1499
1724
  message = message.replace(/[\t\n]/g, '');
1725
+ // Remove non-printable characters
1726
+ // eslint-disable-next-line no-control-regex
1500
1727
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1728
+ // Replace all occurrences of \" with "
1501
1729
  message = message.replace(/\\"/g, '"');
1730
+ // Define the maximum allowed length for continuous characters without a space
1502
1731
  const maxContinuousLength = 100;
1503
1732
  const keepStartLength = 20;
1504
1733
  const keepEndLength = 20;
1734
+ // Split the message into words
1505
1735
  message = message
1506
1736
  .split(' ')
1507
1737
  .map((word) => {
1738
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1508
1739
  if (word.length > maxContinuousLength) {
1509
1740
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1510
1741
  }
1511
1742
  return word;
1512
1743
  })
1513
1744
  .join(' ');
1745
+ // Send the message to all connected clients
1514
1746
  this.webSocketServer?.clients.forEach((client) => {
1515
1747
  if (client.readyState === WebSocket.OPEN) {
1516
1748
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1517
1749
  }
1518
1750
  });
1519
1751
  }
1752
+ /**
1753
+ * Sends a need to refresh WebSocket message to all connected clients.
1754
+ *
1755
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1756
+ * possible values:
1757
+ * - 'matterbridgeLatestVersion'
1758
+ * - 'matterbridgeAdvertise'
1759
+ * - 'online'
1760
+ * - 'offline'
1761
+ * - 'reachability'
1762
+ * - 'settings'
1763
+ * - 'plugins'
1764
+ * - 'devices'
1765
+ * - 'fabrics'
1766
+ * - 'sessions'
1767
+ */
1520
1768
  wssSendRefreshRequired(changed = null) {
1521
1769
  this.log.debug('Sending a refresh required message to all connected clients');
1770
+ // Send the message to all connected clients
1522
1771
  this.webSocketServer?.clients.forEach((client) => {
1523
1772
  if (client.readyState === WebSocket.OPEN) {
1524
1773
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1525
1774
  }
1526
1775
  });
1527
1776
  }
1777
+ /**
1778
+ * Sends a need to restart WebSocket message to all connected clients.
1779
+ *
1780
+ */
1528
1781
  wssSendRestartRequired(snackbar = true) {
1529
1782
  this.log.debug('Sending a restart required message to all connected clients');
1530
1783
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1531
1784
  if (snackbar === true)
1532
1785
  this.wssSendSnackbarMessage(`Restart required`, 0);
1786
+ // Send the message to all connected clients
1533
1787
  this.webSocketServer?.clients.forEach((client) => {
1534
1788
  if (client.readyState === WebSocket.OPEN) {
1535
1789
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1536
1790
  }
1537
1791
  });
1538
1792
  }
1793
+ /**
1794
+ * Sends a need to update WebSocket message to all connected clients.
1795
+ *
1796
+ */
1539
1797
  wssSendUpdateRequired() {
1540
1798
  this.log.debug('Sending an update required message to all connected clients');
1541
1799
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1800
+ // Send the message to all connected clients
1542
1801
  this.webSocketServer?.clients.forEach((client) => {
1543
1802
  if (client.readyState === WebSocket.OPEN) {
1544
1803
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1545
1804
  }
1546
1805
  });
1547
1806
  }
1807
+ /**
1808
+ * Sends a memory update message to all connected clients.
1809
+ *
1810
+ */
1548
1811
  wssSendCpuUpdate(cpuUsage) {
1549
1812
  this.log.debug('Sending a cpu update message to all connected clients');
1813
+ // Send the message to all connected clients
1550
1814
  this.webSocketServer?.clients.forEach((client) => {
1551
1815
  if (client.readyState === WebSocket.OPEN) {
1552
1816
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1553
1817
  }
1554
1818
  });
1555
1819
  }
1820
+ /**
1821
+ * Sends a cpu update message to all connected clients.
1822
+ *
1823
+ */
1556
1824
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1557
1825
  this.log.debug('Sending a memory update message to all connected clients');
1826
+ // Send the message to all connected clients
1558
1827
  this.webSocketServer?.clients.forEach((client) => {
1559
1828
  if (client.readyState === WebSocket.OPEN) {
1560
1829
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1561
1830
  }
1562
1831
  });
1563
1832
  }
1833
+ /**
1834
+ * Sends a memory update message to all connected clients.
1835
+ *
1836
+ */
1564
1837
  wssSendUptimeUpdate(systemUptime, processUptime) {
1565
1838
  this.log.debug('Sending a uptime update message to all connected clients');
1839
+ // Send the message to all connected clients
1566
1840
  this.webSocketServer?.clients.forEach((client) => {
1567
1841
  if (client.readyState === WebSocket.OPEN) {
1568
1842
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1569
1843
  }
1570
1844
  });
1571
1845
  }
1846
+ /**
1847
+ * Sends a cpu update message to all connected clients.
1848
+ * @param {string} message - The message to send.
1849
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1850
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1851
+ *
1852
+ */
1572
1853
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1573
1854
  this.log.debug('Sending a snackbar message to all connected clients');
1855
+ // Send the message to all connected clients
1574
1856
  this.webSocketServer?.clients.forEach((client) => {
1575
1857
  if (client.readyState === WebSocket.OPEN) {
1576
1858
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout, severity } }));
1577
1859
  }
1578
1860
  });
1579
1861
  }
1862
+ /**
1863
+ * Sends an attribute update message to all connected WebSocket clients.
1864
+ *
1865
+ * @param {string | undefined} plugin - The name of the plugin.
1866
+ * @param {string | undefined} serialNumber - The serial number of the device.
1867
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
1868
+ * @param {string} cluster - The cluster name where the attribute belongs.
1869
+ * @param {string} attribute - The name of the attribute that changed.
1870
+ * @param {number | string | boolean} value - The new value of the attribute.
1871
+ *
1872
+ * @remarks
1873
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1874
+ * with the updated attribute information.
1875
+ */
1580
1876
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1581
1877
  this.log.debug('Sending an attribute update message to all connected clients');
1878
+ // Send the message to all connected clients
1582
1879
  this.webSocketServer?.clients.forEach((client) => {
1583
1880
  if (client.readyState === WebSocket.OPEN) {
1584
1881
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1585
1882
  }
1586
1883
  });
1587
1884
  }
1885
+ /**
1886
+ * Sends a message to all connected clients.
1887
+ * @param {number} id - The message id.
1888
+ * @param {string} method - The message method.
1889
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1890
+ *
1891
+ */
1588
1892
  wssBroadcastMessage(id, method, params) {
1589
1893
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1894
+ // Send the message to all connected clients
1590
1895
  this.webSocketServer?.clients.forEach((client) => {
1591
1896
  if (client.readyState === WebSocket.OPEN) {
1592
1897
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1594,3 +1899,4 @@ export class Frontend {
1594
1899
  });
1595
1900
  }
1596
1901
  }
1902
+ //# sourceMappingURL=frontend.js.map