matterbridge 2.2.4-dev.3 → 2.2.4

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