matterbridge 3.0.0-edge.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +50 -9
  2. package/README-DEV.md +4 -0
  3. package/README-DOCKER.md +21 -12
  4. package/README-SERVICE.md +27 -21
  5. package/README.md +80 -2
  6. package/dist/cli.d.ts +29 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +41 -3
  9. package/dist/cli.js.map +1 -0
  10. package/dist/cluster/export.d.ts +2 -0
  11. package/dist/cluster/export.d.ts.map +1 -0
  12. package/dist/cluster/export.js +2 -0
  13. package/dist/cluster/export.js.map +1 -0
  14. package/dist/defaultConfigSchema.d.ts +27 -0
  15. package/dist/defaultConfigSchema.d.ts.map +1 -0
  16. package/dist/defaultConfigSchema.js +23 -0
  17. package/dist/defaultConfigSchema.js.map +1 -0
  18. package/dist/deviceManager.d.ts +114 -0
  19. package/dist/deviceManager.d.ts.map +1 -0
  20. package/dist/deviceManager.js +94 -1
  21. package/dist/deviceManager.js.map +1 -0
  22. package/dist/frontend.d.ts +222 -0
  23. package/dist/frontend.d.ts.map +1 -0
  24. package/dist/frontend.js +443 -35
  25. package/dist/frontend.js.map +1 -0
  26. package/dist/index.d.ts +35 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +28 -1
  29. package/dist/index.js.map +1 -0
  30. package/dist/logger/export.d.ts +2 -0
  31. package/dist/logger/export.d.ts.map +1 -0
  32. package/dist/logger/export.js +1 -0
  33. package/dist/logger/export.js.map +1 -0
  34. package/dist/matter/behaviors.d.ts +2 -0
  35. package/dist/matter/behaviors.d.ts.map +1 -0
  36. package/dist/matter/behaviors.js +2 -0
  37. package/dist/matter/behaviors.js.map +1 -0
  38. package/dist/matter/clusters.d.ts +2 -0
  39. package/dist/matter/clusters.d.ts.map +1 -0
  40. package/dist/matter/clusters.js +2 -0
  41. package/dist/matter/clusters.js.map +1 -0
  42. package/dist/matter/devices.d.ts +2 -0
  43. package/dist/matter/devices.d.ts.map +1 -0
  44. package/dist/matter/devices.js +2 -0
  45. package/dist/matter/devices.js.map +1 -0
  46. package/dist/matter/endpoints.d.ts +2 -0
  47. package/dist/matter/endpoints.d.ts.map +1 -0
  48. package/dist/matter/endpoints.js +2 -0
  49. package/dist/matter/endpoints.js.map +1 -0
  50. package/dist/matter/export.d.ts +5 -0
  51. package/dist/matter/export.d.ts.map +1 -0
  52. package/dist/matter/export.js +2 -0
  53. package/dist/matter/export.js.map +1 -0
  54. package/dist/matter/types.d.ts +3 -0
  55. package/dist/matter/types.d.ts.map +1 -0
  56. package/dist/matter/types.js +2 -0
  57. package/dist/matter/types.js.map +1 -0
  58. package/dist/matterbridge.d.ts +431 -0
  59. package/dist/matterbridge.d.ts.map +1 -0
  60. package/dist/matterbridge.js +800 -79
  61. package/dist/matterbridge.js.map +1 -0
  62. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  63. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  64. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  65. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  66. package/dist/matterbridgeBehaviors.d.ts +1514 -0
  67. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  68. package/dist/matterbridgeBehaviors.js +33 -1
  69. package/dist/matterbridgeBehaviors.js.map +1 -0
  70. package/dist/matterbridgeDeviceTypes.d.ts +494 -0
  71. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  72. package/dist/matterbridgeDeviceTypes.js +431 -12
  73. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  74. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  75. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  76. package/dist/matterbridgeDynamicPlatform.js +34 -0
  77. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  78. package/dist/matterbridgeEndpoint.d.ts +943 -0
  79. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpoint.js +806 -7
  81. package/dist/matterbridgeEndpoint.js.map +1 -0
  82. package/dist/matterbridgeEndpointHelpers.d.ts +2706 -0
  83. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  84. package/dist/matterbridgeEndpointHelpers.js +156 -9
  85. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  86. package/dist/matterbridgePlatform.d.ts +294 -0
  87. package/dist/matterbridgePlatform.d.ts.map +1 -0
  88. package/dist/matterbridgePlatform.js +227 -9
  89. package/dist/matterbridgePlatform.js.map +1 -0
  90. package/dist/matterbridgeTypes.d.ts +187 -0
  91. package/dist/matterbridgeTypes.d.ts.map +1 -0
  92. package/dist/matterbridgeTypes.js +24 -0
  93. package/dist/matterbridgeTypes.js.map +1 -0
  94. package/dist/pluginManager.d.ts +271 -0
  95. package/dist/pluginManager.d.ts.map +1 -0
  96. package/dist/pluginManager.js +262 -3
  97. package/dist/pluginManager.js.map +1 -0
  98. package/dist/shelly.d.ts +92 -0
  99. package/dist/shelly.d.ts.map +1 -0
  100. package/dist/shelly.js +146 -6
  101. package/dist/shelly.js.map +1 -0
  102. package/dist/storage/export.d.ts +2 -0
  103. package/dist/storage/export.d.ts.map +1 -0
  104. package/dist/storage/export.js +1 -0
  105. package/dist/storage/export.js.map +1 -0
  106. package/dist/update.d.ts +32 -0
  107. package/dist/update.d.ts.map +1 -0
  108. package/dist/update.js +70 -0
  109. package/dist/update.js.map +1 -0
  110. package/dist/utils/colorUtils.d.ts +61 -0
  111. package/dist/utils/colorUtils.d.ts.map +1 -0
  112. package/dist/utils/colorUtils.js +205 -2
  113. package/dist/utils/colorUtils.js.map +1 -0
  114. package/dist/utils/copyDirectory.d.ts +32 -0
  115. package/dist/utils/copyDirectory.d.ts.map +1 -0
  116. package/dist/utils/copyDirectory.js +37 -1
  117. package/dist/utils/copyDirectory.js.map +1 -0
  118. package/dist/utils/createZip.d.ts +38 -0
  119. package/dist/utils/createZip.d.ts.map +1 -0
  120. package/dist/utils/createZip.js +42 -2
  121. package/dist/utils/createZip.js.map +1 -0
  122. package/dist/utils/deepCopy.d.ts +31 -0
  123. package/dist/utils/deepCopy.d.ts.map +1 -0
  124. package/dist/utils/deepCopy.js +40 -0
  125. package/dist/utils/deepCopy.js.map +1 -0
  126. package/dist/utils/deepEqual.d.ts +53 -0
  127. package/dist/utils/deepEqual.d.ts.map +1 -0
  128. package/dist/utils/deepEqual.js +65 -1
  129. package/dist/utils/deepEqual.js.map +1 -0
  130. package/dist/utils/export.d.ts +10 -0
  131. package/dist/utils/export.d.ts.map +1 -0
  132. package/dist/utils/export.js +1 -0
  133. package/dist/utils/export.js.map +1 -0
  134. package/dist/utils/isvalid.d.ts +87 -0
  135. package/dist/utils/isvalid.d.ts.map +1 -0
  136. package/dist/utils/isvalid.js +86 -0
  137. package/dist/utils/isvalid.js.map +1 -0
  138. package/dist/utils/network.d.ts +69 -0
  139. package/dist/utils/network.d.ts.map +1 -0
  140. package/dist/utils/network.js +76 -5
  141. package/dist/utils/network.js.map +1 -0
  142. package/dist/utils/parameter.d.ts +58 -0
  143. package/dist/utils/parameter.d.ts.map +1 -0
  144. package/dist/utils/parameter.js +92 -7
  145. package/dist/utils/parameter.js.map +1 -0
  146. package/dist/utils/wait.d.ts +43 -0
  147. package/dist/utils/wait.d.ts.map +1 -0
  148. package/dist/utils/wait.js +50 -5
  149. package/dist/utils/wait.js.map +1 -0
  150. package/frontend/build/asset-manifest.json +6 -6
  151. package/frontend/build/bmc-button.svg +22 -0
  152. package/frontend/build/discord.svg +5 -0
  153. package/frontend/build/index.html +1 -1
  154. package/frontend/build/static/css/{main.ea7910e9.css → main.944b63c3.css} +2 -2
  155. package/frontend/build/static/css/main.944b63c3.css.map +1 -0
  156. package/frontend/build/static/js/{main.e11d6bb4.js → main.1d983660.js} +12 -12
  157. package/frontend/build/static/js/{main.e11d6bb4.js.map → main.1d983660.js.map} +1 -1
  158. package/npm-shrinkwrap.json +300 -362
  159. package/package.json +5 -4
  160. package/tsconfig.jest.json +8 -0
  161. package/README-EDGE.md +0 -74
  162. package/frontend/build/static/css/main.ea7910e9.css.map +0 -1
  163. /package/frontend/build/static/js/{main.e11d6bb4.js.LICENSE.txt → main.1d983660.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,27 +1,106 @@
1
+ /**
2
+ * This file contains the class Frontend.
3
+ *
4
+ * @file frontend.ts
5
+ * @author Luca Liguori
6
+ * @date 2025-01-13
7
+ * @version 1.0.2
8
+ *
9
+ * Copyright 2025, 2026, 2027 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // @matter
1
24
  import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
25
+ // Node modules
2
26
  import { createServer } from 'node:http';
3
27
  import https from 'node:https';
4
28
  import os from 'node:os';
5
29
  import path from 'node:path';
6
30
  import { promises as fs } from 'node:fs';
31
+ // Third-party modules
7
32
  import express from 'express';
8
33
  import WebSocket, { WebSocketServer } from 'ws';
9
34
  import multer from 'multer';
35
+ // AnsiLogger module
10
36
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
37
+ // Matterbridge
11
38
  import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString } from './utils/export.js';
12
39
  import { plg } from './matterbridgeTypes.js';
13
40
  import { hasParameter } from './utils/export.js';
14
- import { BridgedDeviceBasicInformation } from '@matter/main/clusters';
41
+ import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
42
+ /**
43
+ * Websocket message ID for logging.
44
+ * @constant {number}
45
+ */
15
46
  export const WS_ID_LOG = 0;
47
+ /**
48
+ * Websocket message ID indicating a refresh is needed.
49
+ * @constant {number}
50
+ */
16
51
  export const WS_ID_REFRESH_NEEDED = 1;
52
+ /**
53
+ * Websocket message ID indicating a restart is needed.
54
+ * @constant {number}
55
+ */
17
56
  export const WS_ID_RESTART_NEEDED = 2;
57
+ /**
58
+ * Websocket message ID indicating a cpu update.
59
+ * @constant {number}
60
+ */
18
61
  export const WS_ID_CPU_UPDATE = 3;
62
+ /**
63
+ * Websocket message ID indicating a memory update.
64
+ * @constant {number}
65
+ */
19
66
  export const WS_ID_MEMORY_UPDATE = 4;
67
+ /**
68
+ * Websocket message ID indicating an uptime update.
69
+ * @constant {number}
70
+ */
20
71
  export const WS_ID_UPTIME_UPDATE = 5;
72
+ /**
73
+ * Websocket message ID indicating a snackbar message.
74
+ * @constant {number}
75
+ */
21
76
  export const WS_ID_SNACKBAR = 6;
77
+ /**
78
+ * Websocket message ID indicating matterbridge has un update available.
79
+ * @constant {number}
80
+ */
22
81
  export const WS_ID_UPDATE_NEEDED = 7;
82
+ /**
83
+ * Websocket message ID indicating a state update.
84
+ * @constant {number}
85
+ */
23
86
  export const WS_ID_STATEUPDATE = 8;
87
+ /**
88
+ * Websocket message ID indicating a shelly system update.
89
+ * check:
90
+ * curl -k http://127.0.0.1:8101/api/updates/sys/check
91
+ * perform:
92
+ * curl -k http://127.0.0.1:8101/api/updates/sys/perform
93
+ * @constant {number}
94
+ */
24
95
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
96
+ /**
97
+ * Websocket message ID indicating a shelly main update.
98
+ * check:
99
+ * curl -k http://127.0.0.1:8101/api/updates/main/check
100
+ * perform:
101
+ * curl -k http://127.0.0.1:8101/api/updates/main/perform
102
+ * @constant {number}
103
+ */
25
104
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
26
105
  export class Frontend {
27
106
  matterbridge;
@@ -39,7 +118,7 @@ export class Frontend {
39
118
  memoryTimeout;
40
119
  constructor(matterbridge) {
41
120
  this.matterbridge = matterbridge;
42
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
121
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
43
122
  }
44
123
  set logLevel(logLevel) {
45
124
  this.log.logLevel = logLevel;
@@ -47,13 +126,43 @@ export class Frontend {
47
126
  async start(port = 8283) {
48
127
  this.port = port;
49
128
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
129
+ // Initialize multer with the upload directory
50
130
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
51
131
  await fs.mkdir(uploadDir, { recursive: true });
52
132
  const upload = multer({ dest: uploadDir });
133
+ // Create the express app that serves the frontend
53
134
  this.expressApp = express();
135
+ // Inject logging/debug wrapper for route/middleware registration
136
+ /*
137
+ const methods = ['get', 'post', 'put', 'delete', 'use'];
138
+ for (const method of methods) {
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ const original = (this.expressApp as any)[method].bind(this.expressApp);
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ (this.expressApp as any)[method] = (path: any, ...rest: any) => {
143
+ try {
144
+ console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
145
+ return original(path, ...rest);
146
+ } catch (err) {
147
+ console.error(`[ERROR] Failed to register route: ${path}`);
148
+ throw err;
149
+ }
150
+ };
151
+ }
152
+ */
153
+ // Log all requests to the server for debugging
154
+ /*
155
+ this.expressApp.use((req, res, next) => {
156
+ this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
157
+ next();
158
+ });
159
+ */
160
+ // Serve static files from '/static' endpoint
54
161
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
55
162
  if (!hasParameter('ssl')) {
163
+ // Create an HTTP server and attach the express app
56
164
  this.httpServer = createServer(this.expressApp);
165
+ // Listen on the specified port
57
166
  if (hasParameter('ingress')) {
58
167
  this.httpServer.listen(this.port, '0.0.0.0', () => {
59
168
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -67,6 +176,7 @@ export class Frontend {
67
176
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
68
177
  });
69
178
  }
179
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
180
  this.httpServer.on('error', (error) => {
71
181
  this.log.error(`Frontend http server error listening on ${this.port}`);
72
182
  switch (error.code) {
@@ -82,6 +192,7 @@ export class Frontend {
82
192
  });
83
193
  }
84
194
  else {
195
+ // Load the SSL certificate, the private key and optionally the CA certificate
85
196
  let cert;
86
197
  try {
87
198
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -109,7 +220,9 @@ export class Frontend {
109
220
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
110
221
  }
111
222
  const serverOptions = { cert, key, ca };
223
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
112
224
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
225
+ // Listen on the specified port
113
226
  if (hasParameter('ingress')) {
114
227
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
115
228
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -123,6 +236,7 @@ export class Frontend {
123
236
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
124
237
  });
125
238
  }
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
240
  this.httpsServer.on('error', (error) => {
127
241
  this.log.error(`Frontend https server error listening on ${this.port}`);
128
242
  switch (error.code) {
@@ -139,16 +253,18 @@ export class Frontend {
139
253
  }
140
254
  if (this.initializeError)
141
255
  return;
256
+ // Create a WebSocket server and attach it to the http or https server
142
257
  const wssPort = this.port;
143
258
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
144
259
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
145
260
  this.webSocketServer.on('connection', (ws, request) => {
146
261
  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";
262
+ // Set the global logger callback for the WebSocketServer
263
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
264
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
265
+ callbackLogLevel = "info" /* LogLevel.INFO */;
266
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
267
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
152
268
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
153
269
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
154
270
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -182,6 +298,7 @@ export class Frontend {
182
298
  this.webSocketServer.on('error', (ws, error) => {
183
299
  this.log.error(`WebSocketServer error: ${error}`);
184
300
  });
301
+ // Subscribe to cli events
185
302
  const { cliEmitter } = await import('./cli.js');
186
303
  cliEmitter.removeAllListeners();
187
304
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -193,6 +310,7 @@ export class Frontend {
193
310
  cliEmitter.on('cpu', (cpuUsage) => {
194
311
  this.wssSendCpuUpdate(cpuUsage);
195
312
  });
313
+ // Endpoint to validate login code
196
314
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
197
315
  const { password } = req.body;
198
316
  this.log.debug('The frontend sent /api/login', password);
@@ -211,23 +329,27 @@ export class Frontend {
211
329
  this.log.warn('/api/login error wrong password');
212
330
  res.json({ valid: false });
213
331
  }
332
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
214
333
  }
215
334
  catch (error) {
216
335
  this.log.error('/api/login error getting password');
217
336
  res.json({ valid: false });
218
337
  }
219
338
  });
339
+ // Endpoint to provide health check
220
340
  this.expressApp.get('/health', (req, res) => {
221
341
  this.log.debug('Express received /health');
222
342
  const healthStatus = {
223
- status: 'ok',
224
- uptime: process.uptime(),
225
- timestamp: new Date().toISOString(),
343
+ status: 'ok', // Indicate service is healthy
344
+ uptime: process.uptime(), // Server uptime in seconds
345
+ timestamp: new Date().toISOString(), // Current timestamp
226
346
  };
227
347
  res.status(200).json(healthStatus);
228
348
  });
349
+ // Endpoint to provide memory usage details
229
350
  this.expressApp.get('/memory', async (req, res) => {
230
351
  this.log.debug('Express received /memory');
352
+ // Memory usage from process
231
353
  const memoryUsageRaw = process.memoryUsage();
232
354
  const memoryUsage = {
233
355
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -236,10 +358,13 @@ export class Frontend {
236
358
  external: this.formatMemoryUsage(memoryUsageRaw.external),
237
359
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
238
360
  };
361
+ // V8 heap statistics
239
362
  const { default: v8 } = await import('node:v8');
240
363
  const heapStatsRaw = v8.getHeapStatistics();
241
364
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
365
+ // Format heapStats
242
366
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
367
+ // Format heapSpaces
243
368
  const heapSpaces = heapSpacesRaw.map((space) => ({
244
369
  ...space,
245
370
  space_size: this.formatMemoryUsage(space.space_size),
@@ -257,6 +382,7 @@ export class Frontend {
257
382
  };
258
383
  res.status(200).json(memoryReport);
259
384
  });
385
+ // Endpoint to start advertising the server node
260
386
  this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
261
387
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
262
388
  if (pairingCodes) {
@@ -267,18 +393,22 @@ export class Frontend {
267
393
  res.status(500).json({ error: 'Failed to generate pairing codes' });
268
394
  }
269
395
  });
396
+ // Endpoint to provide settings
270
397
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
271
398
  this.log.debug('The frontend sent /api/settings');
272
399
  res.json(await this.getApiSettings());
273
400
  });
401
+ // Endpoint to provide plugins
274
402
  this.expressApp.get('/api/plugins', async (req, res) => {
275
403
  this.log.debug('The frontend sent /api/plugins');
276
404
  res.json(this.getBaseRegisteredPlugins());
277
405
  });
406
+ // Endpoint to provide devices
278
407
  this.expressApp.get('/api/devices', (req, res) => {
279
408
  this.log.debug('The frontend sent /api/devices');
280
409
  const devices = [];
281
410
  this.matterbridge.devices.forEach(async (device) => {
411
+ // Check if the device has the required properties
282
412
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
283
413
  return;
284
414
  const cluster = this.getClusterTextFromDevice(device);
@@ -297,6 +427,7 @@ export class Frontend {
297
427
  });
298
428
  res.json(devices);
299
429
  });
430
+ // Endpoint to provide the cluster servers of the devices
300
431
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
301
432
  const selectedPluginName = req.params.selectedPluginName;
302
433
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -369,63 +500,107 @@ export class Frontend {
369
500
  });
370
501
  res.json(data);
371
502
  });
372
- this.expressApp.get('/api/view-log', async (req, res) => {
373
- this.log.debug('The frontend sent /api/log');
503
+ // Endpoint to view the matterbridge log
504
+ this.expressApp.get('/api/view-mblog', async (req, res) => {
505
+ this.log.debug('The frontend sent /api/view-mblog');
374
506
  try {
375
507
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
376
508
  res.type('text/plain');
377
509
  res.send(data);
378
510
  }
379
511
  catch (error) {
380
- this.log.error(`Error reading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
381
- res.status(500).send('Error reading log file');
512
+ this.log.error(`Error reading matterbridge log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
513
+ res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
514
+ }
515
+ });
516
+ // Endpoint to view the matter.js log
517
+ this.expressApp.get('/api/view-mjlog', async (req, res) => {
518
+ this.log.debug('The frontend sent /api/view-mjlog');
519
+ try {
520
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
521
+ res.type('text/plain');
522
+ res.send(data);
523
+ }
524
+ catch (error) {
525
+ this.log.error(`Error reading matter log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
526
+ res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
382
527
  }
383
528
  });
529
+ // Endpoint to view the shelly log
530
+ this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
531
+ this.log.debug('The frontend sent /api/shellyviewsystemlog');
532
+ try {
533
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
534
+ res.type('text/plain');
535
+ res.send(data);
536
+ }
537
+ catch (error) {
538
+ this.log.error(`Error reading shelly log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
539
+ res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
540
+ }
541
+ });
542
+ // Endpoint to download the matterbridge log
384
543
  this.expressApp.get('/api/download-mblog', async (req, res) => {
385
- this.log.debug('The frontend sent /api/download-mblog');
544
+ this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
386
545
  try {
387
546
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
547
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
548
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
388
549
  }
389
550
  catch (error) {
390
- fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
551
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
552
+ this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
391
553
  }
392
- res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
554
+ res.type('text/plain');
555
+ // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
556
+ res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
393
557
  if (error) {
394
558
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
395
559
  res.status(500).send('Error downloading the matterbridge log file');
396
560
  }
397
561
  });
398
562
  });
563
+ // Endpoint to download the matter log
399
564
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
400
- this.log.debug('The frontend sent /api/download-mjlog');
565
+ this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
401
566
  try {
402
567
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
568
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
569
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
403
570
  }
404
571
  catch (error) {
405
- fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
572
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
573
+ this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
406
574
  }
407
- res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
575
+ res.type('text/plain');
576
+ res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
408
577
  if (error) {
409
578
  this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
410
579
  res.status(500).send('Error downloading the matter log file');
411
580
  }
412
581
  });
413
582
  });
583
+ // Endpoint to download the shelly log
414
584
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
415
585
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
416
586
  try {
417
587
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
588
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
589
+ await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
418
590
  }
419
591
  catch (error) {
420
- fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'Create the Shelly system log before downloading it.');
592
+ await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), 'Create the Shelly system log before downloading it.', 'utf-8');
593
+ this.log.debug(`Error in /api/shellydownloadsystemlog: ${error instanceof Error ? error.message : error}`);
421
594
  }
422
- res.download(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'shelly.log', (error) => {
595
+ res.type('text/plain');
596
+ res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
423
597
  if (error) {
424
598
  this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
425
599
  res.status(500).send('Error downloading Shelly system log file');
426
600
  }
427
601
  });
428
602
  });
603
+ // Endpoint to download the matter storage file
429
604
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
430
605
  this.log.debug('The frontend sent /api/download-mjstorage');
431
606
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -436,6 +611,7 @@ export class Frontend {
436
611
  }
437
612
  });
438
613
  });
614
+ // Endpoint to download the matterbridge storage directory
439
615
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
440
616
  this.log.debug('The frontend sent /api/download-mbstorage');
441
617
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -446,6 +622,7 @@ export class Frontend {
446
622
  }
447
623
  });
448
624
  });
625
+ // Endpoint to download the matterbridge plugin directory
449
626
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
450
627
  this.log.debug('The frontend sent /api/download-pluginstorage');
451
628
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -456,9 +633,11 @@ export class Frontend {
456
633
  }
457
634
  });
458
635
  });
636
+ // Endpoint to download the matterbridge plugin config files
459
637
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
460
638
  this.log.debug('The frontend sent /api/download-pluginconfig');
461
639
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
640
+ // 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
641
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
463
642
  if (error) {
464
643
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -466,6 +645,7 @@ export class Frontend {
466
645
  }
467
646
  });
468
647
  });
648
+ // Endpoint to download the matterbridge plugin config files
469
649
  this.expressApp.get('/api/download-backup', async (req, res) => {
470
650
  this.log.debug('The frontend sent /api/download-backup');
471
651
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -475,6 +655,7 @@ export class Frontend {
475
655
  }
476
656
  });
477
657
  });
658
+ // Endpoint to receive commands
478
659
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
479
660
  const command = req.params.command;
480
661
  let param = req.params.param;
@@ -484,13 +665,15 @@ export class Frontend {
484
665
  return;
485
666
  }
486
667
  this.log.debug(`Received frontend command: ${command}:${param}`);
668
+ // Handle the command setpassword from Settings
487
669
  if (command === 'setpassword') {
488
- const password = param.slice(1, -1);
670
+ const password = param.slice(1, -1); // Remove the first and last characters
489
671
  this.log.debug('setpassword', param, password);
490
672
  await this.matterbridge.nodeContext?.set('password', password);
491
673
  res.json({ message: 'Command received' });
492
674
  return;
493
675
  }
676
+ // Handle the command setbridgemode from Settings
494
677
  if (command === 'setbridgemode') {
495
678
  this.log.debug(`setbridgemode: ${param}`);
496
679
  this.wssSendRestartRequired();
@@ -498,6 +681,7 @@ export class Frontend {
498
681
  res.json({ message: 'Command received' });
499
682
  return;
500
683
  }
684
+ // Handle the command backup from Settings
501
685
  if (command === 'backup') {
502
686
  this.log.notice(`Prepairing the backup...`);
503
687
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -506,31 +690,33 @@ export class Frontend {
506
690
  res.json({ message: 'Command received' });
507
691
  return;
508
692
  }
693
+ // Handle the command setmbloglevel from Settings
509
694
  if (command === 'setmbloglevel') {
510
695
  this.log.debug('Matterbridge log level:', param);
511
696
  if (param === 'Debug') {
512
- this.log.logLevel = "debug";
697
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
513
698
  }
514
699
  else if (param === 'Info') {
515
- this.log.logLevel = "info";
700
+ this.log.logLevel = "info" /* LogLevel.INFO */;
516
701
  }
517
702
  else if (param === 'Notice') {
518
- this.log.logLevel = "notice";
703
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
519
704
  }
520
705
  else if (param === 'Warn') {
521
- this.log.logLevel = "warn";
706
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
522
707
  }
523
708
  else if (param === 'Error') {
524
- this.log.logLevel = "error";
709
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
525
710
  }
526
711
  else if (param === 'Fatal') {
527
- this.log.logLevel = "fatal";
712
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
528
713
  }
529
714
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
530
715
  await this.matterbridge.setLogLevel(this.log.logLevel);
531
716
  res.json({ message: 'Command received' });
532
717
  return;
533
718
  }
719
+ // Handle the command setmbloglevel from Settings
534
720
  if (command === 'setmjloglevel') {
535
721
  this.log.debug('Matter.js log level:', param);
536
722
  if (param === 'Debug') {
@@ -555,6 +741,7 @@ export class Frontend {
555
741
  res.json({ message: 'Command received' });
556
742
  return;
557
743
  }
744
+ // Handle the command setmdnsinterface from Settings
558
745
  if (command === 'setmdnsinterface') {
559
746
  if (param === 'json' && isValidString(req.body.value)) {
560
747
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = req.body.value;
@@ -564,6 +751,7 @@ export class Frontend {
564
751
  res.json({ message: 'Command received' });
565
752
  return;
566
753
  }
754
+ // Handle the command setipv4address from Settings
567
755
  if (command === 'setipv4address') {
568
756
  if (param === 'json' && isValidString(req.body.value)) {
569
757
  this.log.debug(`Matter.js ipv4 address: ${req.body.value === '' ? 'all ipv4 addresses' : req.body.value}`);
@@ -573,6 +761,7 @@ export class Frontend {
573
761
  res.json({ message: 'Command received' });
574
762
  return;
575
763
  }
764
+ // Handle the command setipv6address from Settings
576
765
  if (command === 'setipv6address') {
577
766
  if (param === 'json' && isValidString(req.body.value)) {
578
767
  this.log.debug(`Matter.js ipv6 address: ${req.body.value === '' ? 'all ipv6 addresses' : req.body.value}`);
@@ -582,6 +771,7 @@ export class Frontend {
582
771
  res.json({ message: 'Command received' });
583
772
  return;
584
773
  }
774
+ // Handle the command setmatterport from Settings
585
775
  if (command === 'setmatterport') {
586
776
  const port = Math.min(Math.max(parseInt(req.body.value), 5540), 5560);
587
777
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -590,6 +780,7 @@ export class Frontend {
590
780
  res.json({ message: 'Command received' });
591
781
  return;
592
782
  }
783
+ // Handle the command setmatterdiscriminator from Settings
593
784
  if (command === 'setmatterdiscriminator') {
594
785
  const discriminator = Math.min(Math.max(parseInt(req.body.value), 1000), 4095);
595
786
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -598,6 +789,7 @@ export class Frontend {
598
789
  res.json({ message: 'Command received' });
599
790
  return;
600
791
  }
792
+ // Handle the command setmatterpasscode from Settings
601
793
  if (command === 'setmatterpasscode') {
602
794
  const passcode = Math.min(Math.max(parseInt(req.body.value), 10000000), 90000000);
603
795
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -606,17 +798,20 @@ export class Frontend {
606
798
  res.json({ message: 'Command received' });
607
799
  return;
608
800
  }
801
+ // Handle the command setmbloglevel from Settings
609
802
  if (command === 'setmblogfile') {
610
803
  this.log.debug('Matterbridge file log:', param);
611
804
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
612
805
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
806
+ // Create the file logger for matterbridge
613
807
  if (param === 'true')
614
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
808
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
615
809
  else
616
810
  AnsiLogger.setGlobalLogfile(undefined);
617
811
  res.json({ message: 'Command received' });
618
812
  return;
619
813
  }
814
+ // Handle the command setmbloglevel from Settings
620
815
  if (command === 'setmjlogfile') {
621
816
  this.log.debug('Matter file log:', param);
622
817
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -643,40 +838,48 @@ export class Frontend {
643
838
  res.json({ message: 'Command received' });
644
839
  return;
645
840
  }
841
+ // Handle the command unregister from Settings
646
842
  if (command === 'unregister') {
647
843
  await this.matterbridge.unregisterAndShutdownProcess();
648
844
  res.json({ message: 'Command received' });
649
845
  return;
650
846
  }
847
+ // Handle the command reset from Settings
651
848
  if (command === 'reset') {
652
849
  await this.matterbridge.shutdownProcessAndReset();
653
850
  res.json({ message: 'Command received' });
654
851
  return;
655
852
  }
853
+ // Handle the command factoryreset from Settings
656
854
  if (command === 'factoryreset') {
657
855
  await this.matterbridge.shutdownProcessAndFactoryReset();
658
856
  res.json({ message: 'Command received' });
659
857
  return;
660
858
  }
859
+ // Handle the command shutdown from Header
661
860
  if (command === 'shutdown') {
662
861
  await this.matterbridge.shutdownProcess();
663
862
  res.json({ message: 'Command received' });
664
863
  return;
665
864
  }
865
+ // Handle the command restart from Header
666
866
  if (command === 'restart') {
667
867
  await this.matterbridge.restartProcess();
668
868
  res.json({ message: 'Command received' });
669
869
  return;
670
870
  }
871
+ // Handle the command update from Header
671
872
  if (command === 'update') {
672
873
  await this.matterbridge.updateProcess();
673
874
  this.wssSendRestartRequired();
674
875
  res.json({ message: 'Command received' });
675
876
  return;
676
877
  }
878
+ // Handle the command saveconfig from Home
677
879
  if (command === 'saveconfig') {
678
880
  param = param.replace(/\*/g, '\\');
679
881
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
882
+ // console.log('Req.body:', JSON.stringify(req.body, null, 2));
680
883
  if (!this.matterbridge.plugins.has(param)) {
681
884
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
682
885
  }
@@ -691,6 +894,7 @@ export class Frontend {
691
894
  res.json({ message: 'Command received' });
692
895
  return;
693
896
  }
897
+ // Handle the command installplugin from Home
694
898
  if (command === 'installplugin') {
695
899
  param = param.replace(/\*/g, '\\');
696
900
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
@@ -699,6 +903,7 @@ export class Frontend {
699
903
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
700
904
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
701
905
  this.wssSendSnackbarMessage(`Installed package ${param}`, 10, 'success');
906
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
702
907
  }
703
908
  catch (error) {
704
909
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
@@ -706,17 +911,22 @@ export class Frontend {
706
911
  }
707
912
  this.wssSendRestartRequired();
708
913
  param = param.split('@')[0];
914
+ // Also add the plugin to matterbridge so no return!
709
915
  if (param === 'matterbridge') {
916
+ // 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
917
  res.json({ message: 'Command received' });
711
918
  return;
712
919
  }
713
920
  }
921
+ // Handle the command addplugin from Home
714
922
  if (command === 'addplugin' || command === 'installplugin') {
715
923
  param = param.replace(/\*/g, '\\');
716
924
  const plugin = await this.matterbridge.plugins.add(param);
717
925
  if (plugin) {
718
926
  this.wssSendSnackbarMessage(`Added plugin ${param}`);
719
927
  if (this.matterbridge.bridgeMode === 'childbridge') {
928
+ // 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
929
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
720
930
  this.matterbridge.createDynamicPlugin(plugin, true);
721
931
  }
722
932
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
@@ -726,13 +936,14 @@ export class Frontend {
726
936
  res.json({ message: 'Command received' });
727
937
  return;
728
938
  }
939
+ // Handle the command removeplugin from Home
729
940
  if (command === 'removeplugin') {
730
941
  if (!this.matterbridge.plugins.has(param)) {
731
942
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
732
943
  }
733
944
  else {
734
945
  const plugin = this.matterbridge.plugins.get(param);
735
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
946
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
736
947
  await this.matterbridge.plugins.remove(param);
737
948
  this.wssSendSnackbarMessage(`Removed plugin ${param}`);
738
949
  this.wssSendRefreshRequired('plugins');
@@ -740,6 +951,7 @@ export class Frontend {
740
951
  res.json({ message: 'Command received' });
741
952
  return;
742
953
  }
954
+ // Handle the command enableplugin from Home
743
955
  if (command === 'enableplugin') {
744
956
  if (!this.matterbridge.plugins.has(param)) {
745
957
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -758,6 +970,7 @@ export class Frontend {
758
970
  await this.matterbridge.plugins.enable(param);
759
971
  this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
760
972
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
973
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
761
974
  this.matterbridge.createDynamicPlugin(plugin, true);
762
975
  }
763
976
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
@@ -768,6 +981,7 @@ export class Frontend {
768
981
  res.json({ message: 'Command received' });
769
982
  return;
770
983
  }
984
+ // Handle the command disableplugin from Home
771
985
  if (command === 'disableplugin') {
772
986
  if (!this.matterbridge.plugins.has(param)) {
773
987
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -775,7 +989,7 @@ export class Frontend {
775
989
  else {
776
990
  const plugin = this.matterbridge.plugins.get(param);
777
991
  if (plugin && plugin.enabled) {
778
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
992
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
779
993
  await this.matterbridge.plugins.disable(param);
780
994
  this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
781
995
  this.wssSendRefreshRequired('plugins');
@@ -794,10 +1008,13 @@ export class Frontend {
794
1008
  return;
795
1009
  }
796
1010
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`);
1011
+ // Define the path where the plugin file will be saved
797
1012
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
798
1013
  try {
1014
+ // Move the uploaded file to the specified path
799
1015
  await fs.rename(file.path, filePath);
800
1016
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
1017
+ // Install the plugin package
801
1018
  if (filename.endsWith('.tgz')) {
802
1019
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
803
1020
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -814,32 +1031,46 @@ export class Frontend {
814
1031
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
815
1032
  }
816
1033
  });
817
- this.expressApp.get('*', (req, res) => {
1034
+ // Fallback for routing (must be the last route)
1035
+ this.expressApp.use((req, res) => {
818
1036
  this.log.debug('The frontend sent:', req.url);
819
- this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
820
1037
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
821
1038
  });
1039
+ /* Not working in express v5!
1040
+ this.expressApp.get('*', (req, res) => {
1041
+ this.log.debug('The frontend sent:', req.url);
1042
+ this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
1043
+ res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
1044
+ });
1045
+ */
822
1046
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
823
1047
  }
824
1048
  async stop() {
1049
+ // Remove all listeners from the cliEmitter
1050
+ // cliEmitter.removeAllListeners();
1051
+ // Close the http server
825
1052
  if (this.httpServer) {
826
1053
  this.httpServer.close();
827
1054
  this.httpServer.removeAllListeners();
828
1055
  this.httpServer = undefined;
829
1056
  this.log.debug('Frontend http server closed successfully');
830
1057
  }
1058
+ // Close the https server
831
1059
  if (this.httpsServer) {
832
1060
  this.httpsServer.close();
833
1061
  this.httpsServer.removeAllListeners();
834
1062
  this.httpsServer = undefined;
835
1063
  this.log.debug('Frontend https server closed successfully');
836
1064
  }
1065
+ // Remove listeners from the express app
837
1066
  if (this.expressApp) {
838
1067
  this.expressApp.removeAllListeners();
839
1068
  this.expressApp = undefined;
840
1069
  this.log.debug('Frontend app closed successfully');
841
1070
  }
1071
+ // Close the WebSocket server
842
1072
  if (this.webSocketServer) {
1073
+ // Close all active connections
843
1074
  this.webSocketServer.clients.forEach((client) => {
844
1075
  if (client.readyState === WebSocket.OPEN) {
845
1076
  client.close();
@@ -856,6 +1087,7 @@ export class Frontend {
856
1087
  this.webSocketServer = undefined;
857
1088
  }
858
1089
  }
1090
+ // Function to format bytes to KB, MB, or GB
859
1091
  formatMemoryUsage = (bytes) => {
860
1092
  if (bytes >= 1024 ** 3) {
861
1093
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -867,6 +1099,7 @@ export class Frontend {
867
1099
  return `${(bytes / 1024).toFixed(2)} KB`;
868
1100
  }
869
1101
  };
1102
+ // Function to format system uptime with only the most significant unit
870
1103
  formatOsUpTime = (seconds) => {
871
1104
  if (seconds >= 86400) {
872
1105
  const days = Math.floor(seconds / 86400);
@@ -882,8 +1115,13 @@ export class Frontend {
882
1115
  }
883
1116
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
884
1117
  };
1118
+ /**
1119
+ * Retrieves the api settings data.
1120
+ * @returns {Promise<object>} A promise that resolve in the api settings object.
1121
+ */
885
1122
  async getApiSettings() {
886
1123
  const { lastCpuUsage } = await import('./cli.js');
1124
+ // Update the system information
887
1125
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
888
1126
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
889
1127
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -892,6 +1130,7 @@ export class Frontend {
892
1130
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
893
1131
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
894
1132
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
1133
+ // Update the matterbridge information
895
1134
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
896
1135
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
897
1136
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -910,6 +1149,11 @@ export class Frontend {
910
1149
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
911
1150
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
912
1151
  }
1152
+ /**
1153
+ * Retrieves the reachable attribute.
1154
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1155
+ * @returns {boolean} The reachable attribute.
1156
+ */
913
1157
  getReachability(device) {
914
1158
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
915
1159
  return false;
@@ -919,6 +1163,35 @@ export class Frontend {
919
1163
  return true;
920
1164
  return false;
921
1165
  }
1166
+ getPowerSource(device) {
1167
+ if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1168
+ return undefined;
1169
+ const powerSource = (device) => {
1170
+ const featureMap = device.getAttribute(PowerSource.Cluster.id, 'featureMap');
1171
+ if (featureMap.wired) {
1172
+ const wiredCurrentType = device.getAttribute(PowerSource.Cluster.id, 'wiredCurrentType');
1173
+ return ['ac', 'dc'][wiredCurrentType];
1174
+ }
1175
+ if (featureMap.battery) {
1176
+ const batChargeLevel = device.getAttribute(PowerSource.Cluster.id, 'batChargeLevel');
1177
+ return ['ok', 'warning', 'critical'][batChargeLevel];
1178
+ }
1179
+ return;
1180
+ };
1181
+ // Root endpoint
1182
+ if (device.hasClusterServer(PowerSource.Cluster.id))
1183
+ return powerSource(device);
1184
+ // Child endpoints
1185
+ for (const child of device.getChildEndpoints()) {
1186
+ if (child.hasClusterServer(PowerSource.Cluster.id))
1187
+ return powerSource(child);
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Retrieves the cluster text description from a given device.
1192
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
1193
+ * @returns {string} The attributes description of the cluster servers in the device.
1194
+ */
922
1195
  getClusterTextFromDevice(device) {
923
1196
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
924
1197
  return '';
@@ -958,7 +1231,9 @@ export class Frontend {
958
1231
  return '';
959
1232
  };
960
1233
  let attributes = '';
1234
+ let supportedModes = [];
961
1235
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1236
+ // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
962
1237
  if (typeof attributeValue === 'undefined')
963
1238
  return;
964
1239
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -975,6 +1250,20 @@ export class Frontend {
975
1250
  attributes += `Heat to: ${attributeValue / 100}°C `;
976
1251
  if (clusterName === 'thermostat' && attributeName === 'occupiedCoolingSetpoint' && isValidNumber(attributeValue))
977
1252
  attributes += `Cool to: ${attributeValue / 100}°C `;
1253
+ const modeClusters = ['modeSelect', 'rvcRunMode', 'rvcCleanMode', 'laundryWasherMode', 'ovenMode', 'microwaveOvenMode'];
1254
+ if (modeClusters.includes(clusterName) && attributeName === 'supportedModes') {
1255
+ supportedModes = attributeValue;
1256
+ }
1257
+ if (modeClusters.includes(clusterName) && attributeName === 'currentMode') {
1258
+ const supportedMode = supportedModes.find((mode) => mode.mode === attributeValue);
1259
+ if (supportedMode)
1260
+ attributes += `Mode: ${supportedMode.label} `;
1261
+ else
1262
+ attributes += `Mode: ${attributeValue} `;
1263
+ }
1264
+ const operationalStateClusters = ['operationalState', 'rvcOperationalState'];
1265
+ if (operationalStateClusters.includes(clusterName) && attributeName === 'operationalState')
1266
+ attributes += `OpState: ${attributeValue} `;
978
1267
  if (clusterName === 'pumpConfigurationAndControl' && attributeName === 'operationMode')
979
1268
  attributes += `Mode: ${attributeValue} `;
980
1269
  if (clusterName === 'valveConfigurationAndControl' && attributeName === 'currentState')
@@ -1036,8 +1325,13 @@ export class Frontend {
1036
1325
  if (clusterName === 'userLabel' && attributeName === 'labelList')
1037
1326
  attributes += `${getUserLabel(device)} `;
1038
1327
  });
1328
+ // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
1039
1329
  return attributes.trimStart().trimEnd();
1040
1330
  }
1331
+ /**
1332
+ * Retrieves the base registered plugins sanitized for res.json().
1333
+ * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
1334
+ */
1041
1335
  getBaseRegisteredPlugins() {
1042
1336
  const baseRegisteredPlugins = [];
1043
1337
  for (const plugin of this.matterbridge.plugins) {
@@ -1053,6 +1347,7 @@ export class Frontend {
1053
1347
  changelog: plugin.changelog,
1054
1348
  funding: plugin.funding,
1055
1349
  latestVersion: plugin.latestVersion,
1350
+ serialNumber: plugin.serialNumber,
1056
1351
  locked: plugin.locked,
1057
1352
  error: plugin.error,
1058
1353
  enabled: plugin.enabled,
@@ -1074,6 +1369,13 @@ export class Frontend {
1074
1369
  }
1075
1370
  return baseRegisteredPlugins;
1076
1371
  }
1372
+ /**
1373
+ * Handles incoming websocket messages for the Matterbridge frontend.
1374
+ *
1375
+ * @param {WebSocket} client - The websocket client that sent the message.
1376
+ * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1377
+ * @returns {Promise<void>} A promise that resolves when the message has been handled.
1378
+ */
1077
1379
  async wsMessageHandler(client, message) {
1078
1380
  let data;
1079
1381
  try {
@@ -1229,8 +1531,10 @@ export class Frontend {
1229
1531
  else if (data.method === '/api/devices') {
1230
1532
  const devices = [];
1231
1533
  this.matterbridge.devices.forEach(async (device) => {
1534
+ // Filter by pluginName if provided
1232
1535
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1233
1536
  return;
1537
+ // Check if the device has the required properties
1234
1538
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1235
1539
  return;
1236
1540
  const cluster = this.getClusterTextFromDevice(device);
@@ -1244,6 +1548,7 @@ export class Frontend {
1244
1548
  configUrl: device.configUrl,
1245
1549
  uniqueId: device.uniqueId,
1246
1550
  reachable: this.getReachability(device),
1551
+ powerSource: this.getPowerSource(device),
1247
1552
  cluster: cluster,
1248
1553
  });
1249
1554
  });
@@ -1314,6 +1619,7 @@ export class Frontend {
1314
1619
  });
1315
1620
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1316
1621
  deviceTypes = [];
1622
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1317
1623
  const name = childEndpoint.endpoint?.id;
1318
1624
  const clusterServers = childEndpoint.getAllClusterServers();
1319
1625
  clusterServers.forEach((clusterServer) => {
@@ -1396,7 +1702,7 @@ export class Frontend {
1396
1702
  return;
1397
1703
  }
1398
1704
  this.log.notice(`Action ${CYAN}${data.params.action}${nt}${data.params.value ? ' with ' + CYAN + data.params.value + nt : ''} for plugin ${CYAN}${plugin.name}${nt}`);
1399
- plugin.platform?.onAction(data.params.action, data.params.value, data.params.id).catch((error) => {
1705
+ plugin.platform?.onAction(data.params.action, data.params.value, data.params.id, data.params.formData).catch((error) => {
1400
1706
  this.log.error(`Error in plugin ${plugin.name} action ${data.params.action}: ${error}`);
1401
1707
  });
1402
1708
  }
@@ -1412,6 +1718,7 @@ export class Frontend {
1412
1718
  return;
1413
1719
  }
1414
1720
  const config = plugin.configJson;
1721
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1415
1722
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1416
1723
  this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1417
1724
  if (select === 'serial')
@@ -1419,9 +1726,11 @@ export class Frontend {
1419
1726
  if (select === 'name')
1420
1727
  this.log.info(`Selected device name ${data.params.name}`);
1421
1728
  if (config && select && (select === 'serial' || select === 'name')) {
1729
+ // Remove postfix from the serial if it exists
1422
1730
  if (config.postfix) {
1423
1731
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1424
1732
  }
1733
+ // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1425
1734
  if (isValidArray(config.whiteList, 1)) {
1426
1735
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1427
1736
  config.whiteList.push(data.params.serial);
@@ -1430,6 +1739,7 @@ export class Frontend {
1430
1739
  config.whiteList.push(data.params.name);
1431
1740
  }
1432
1741
  }
1742
+ // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1433
1743
  if (isValidArray(config.blackList, 1)) {
1434
1744
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1435
1745
  config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
@@ -1451,6 +1761,7 @@ export class Frontend {
1451
1761
  return;
1452
1762
  }
1453
1763
  const config = plugin.configJson;
1764
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1454
1765
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1455
1766
  this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1456
1767
  if (select === 'serial')
@@ -1461,6 +1772,7 @@ export class Frontend {
1461
1772
  if (config.postfix) {
1462
1773
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1463
1774
  }
1775
+ // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1464
1776
  if (isValidArray(config.whiteList, 1)) {
1465
1777
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1466
1778
  config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
@@ -1469,6 +1781,7 @@ export class Frontend {
1469
1781
  config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
1470
1782
  }
1471
1783
  }
1784
+ // Add the serial to the blackList
1472
1785
  if (isValidArray(config.blackList)) {
1473
1786
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1474
1787
  config.blackList.push(data.params.serial);
@@ -1495,102 +1808,196 @@ export class Frontend {
1495
1808
  return;
1496
1809
  }
1497
1810
  }
1811
+ /**
1812
+ * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1813
+ *
1814
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1815
+ * @param {string} time - The time string of the message
1816
+ * @param {string} name - The logger name of the message
1817
+ * @param {string} message - The content of the message.
1818
+ */
1498
1819
  wssSendMessage(level, time, name, message) {
1499
1820
  if (!level || !time || !name || !message)
1500
1821
  return;
1822
+ // Remove ANSI escape codes from the message
1823
+ // eslint-disable-next-line no-control-regex
1501
1824
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1825
+ // Remove leading asterisks from the message
1502
1826
  message = message.replace(/^\*+/, '');
1827
+ // Replace all occurrences of \t and \n
1503
1828
  message = message.replace(/[\t\n]/g, '');
1829
+ // Remove non-printable characters
1830
+ // eslint-disable-next-line no-control-regex
1504
1831
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1832
+ // Replace all occurrences of \" with "
1505
1833
  message = message.replace(/\\"/g, '"');
1834
+ // Replace all occurrences of angle-brackets with &lt; and &gt;"
1835
+ message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1836
+ // Define the maximum allowed length for continuous characters without a space
1506
1837
  const maxContinuousLength = 100;
1507
1838
  const keepStartLength = 20;
1508
1839
  const keepEndLength = 20;
1840
+ // Split the message into words
1509
1841
  message = message
1510
1842
  .split(' ')
1511
1843
  .map((word) => {
1844
+ // If the word length exceeds the max continuous length, insert spaces and truncate
1512
1845
  if (word.length > maxContinuousLength) {
1513
1846
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1514
1847
  }
1515
1848
  return word;
1516
1849
  })
1517
1850
  .join(' ');
1851
+ // Send the message to all connected clients
1518
1852
  this.webSocketServer?.clients.forEach((client) => {
1519
1853
  if (client.readyState === WebSocket.OPEN) {
1520
1854
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1521
1855
  }
1522
1856
  });
1523
1857
  }
1858
+ /**
1859
+ * Sends a need to refresh WebSocket message to all connected clients.
1860
+ *
1861
+ * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1862
+ * possible values:
1863
+ * - 'matterbridgeLatestVersion'
1864
+ * - 'matterbridgeAdvertise'
1865
+ * - 'online'
1866
+ * - 'offline'
1867
+ * - 'reachability'
1868
+ * - 'settings'
1869
+ * - 'plugins'
1870
+ * - 'devices'
1871
+ * - 'fabrics'
1872
+ * - 'sessions'
1873
+ */
1524
1874
  wssSendRefreshRequired(changed = null) {
1525
1875
  this.log.debug('Sending a refresh required message to all connected clients');
1876
+ // Send the message to all connected clients
1526
1877
  this.webSocketServer?.clients.forEach((client) => {
1527
1878
  if (client.readyState === WebSocket.OPEN) {
1528
1879
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1529
1880
  }
1530
1881
  });
1531
1882
  }
1883
+ /**
1884
+ * Sends a need to restart WebSocket message to all connected clients.
1885
+ *
1886
+ */
1532
1887
  wssSendRestartRequired(snackbar = true) {
1533
1888
  this.log.debug('Sending a restart required message to all connected clients');
1534
1889
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1535
1890
  if (snackbar === true)
1536
1891
  this.wssSendSnackbarMessage(`Restart required`, 0);
1892
+ // Send the message to all connected clients
1537
1893
  this.webSocketServer?.clients.forEach((client) => {
1538
1894
  if (client.readyState === WebSocket.OPEN) {
1539
1895
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1540
1896
  }
1541
1897
  });
1542
1898
  }
1899
+ /**
1900
+ * Sends a need to update WebSocket message to all connected clients.
1901
+ *
1902
+ */
1543
1903
  wssSendUpdateRequired() {
1544
1904
  this.log.debug('Sending an update required message to all connected clients');
1545
1905
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1906
+ // Send the message to all connected clients
1546
1907
  this.webSocketServer?.clients.forEach((client) => {
1547
1908
  if (client.readyState === WebSocket.OPEN) {
1548
1909
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1549
1910
  }
1550
1911
  });
1551
1912
  }
1913
+ /**
1914
+ * Sends a memory update message to all connected clients.
1915
+ *
1916
+ */
1552
1917
  wssSendCpuUpdate(cpuUsage) {
1553
1918
  this.log.debug('Sending a cpu update message to all connected clients');
1919
+ // Send the message to all connected clients
1554
1920
  this.webSocketServer?.clients.forEach((client) => {
1555
1921
  if (client.readyState === WebSocket.OPEN) {
1556
1922
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1557
1923
  }
1558
1924
  });
1559
1925
  }
1926
+ /**
1927
+ * Sends a cpu update message to all connected clients.
1928
+ *
1929
+ */
1560
1930
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1561
1931
  this.log.debug('Sending a memory update message to all connected clients');
1932
+ // Send the message to all connected clients
1562
1933
  this.webSocketServer?.clients.forEach((client) => {
1563
1934
  if (client.readyState === WebSocket.OPEN) {
1564
1935
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1565
1936
  }
1566
1937
  });
1567
1938
  }
1939
+ /**
1940
+ * Sends a memory update message to all connected clients.
1941
+ *
1942
+ */
1568
1943
  wssSendUptimeUpdate(systemUptime, processUptime) {
1569
1944
  this.log.debug('Sending a uptime update message to all connected clients');
1945
+ // Send the message to all connected clients
1570
1946
  this.webSocketServer?.clients.forEach((client) => {
1571
1947
  if (client.readyState === WebSocket.OPEN) {
1572
1948
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1573
1949
  }
1574
1950
  });
1575
1951
  }
1952
+ /**
1953
+ * Sends a cpu update message to all connected clients.
1954
+ * @param {string} message - The message to send.
1955
+ * @param {number} timeout - The timeout in seconds for the snackbar message.
1956
+ * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1957
+ *
1958
+ */
1576
1959
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1577
1960
  this.log.debug('Sending a snackbar message to all connected clients');
1961
+ // Send the message to all connected clients
1578
1962
  this.webSocketServer?.clients.forEach((client) => {
1579
1963
  if (client.readyState === WebSocket.OPEN) {
1580
1964
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout, severity } }));
1581
1965
  }
1582
1966
  });
1583
1967
  }
1968
+ /**
1969
+ * Sends an attribute update message to all connected WebSocket clients.
1970
+ *
1971
+ * @param {string | undefined} plugin - The name of the plugin.
1972
+ * @param {string | undefined} serialNumber - The serial number of the device.
1973
+ * @param {string | undefined} uniqueId - The unique identifier of the device.
1974
+ * @param {string} cluster - The cluster name where the attribute belongs.
1975
+ * @param {string} attribute - The name of the attribute that changed.
1976
+ * @param {number | string | boolean} value - The new value of the attribute.
1977
+ *
1978
+ * @remarks
1979
+ * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1980
+ * with the updated attribute information.
1981
+ */
1584
1982
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1585
1983
  this.log.debug('Sending an attribute update message to all connected clients');
1984
+ // Send the message to all connected clients
1586
1985
  this.webSocketServer?.clients.forEach((client) => {
1587
1986
  if (client.readyState === WebSocket.OPEN) {
1588
1987
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1589
1988
  }
1590
1989
  });
1591
1990
  }
1991
+ /**
1992
+ * Sends a message to all connected clients.
1993
+ * @param {number} id - The message id.
1994
+ * @param {string} method - The message method.
1995
+ * @param {Record<string, string | number | boolean>} params - The message parameters.
1996
+ *
1997
+ */
1592
1998
  wssBroadcastMessage(id, method, params) {
1593
1999
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
2000
+ // Send the message to all connected clients
1594
2001
  this.webSocketServer?.clients.forEach((client) => {
1595
2002
  if (client.readyState === WebSocket.OPEN) {
1596
2003
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1598,3 +2005,4 @@ export class Frontend {
1598
2005
  });
1599
2006
  }
1600
2007
  }
2008
+ //# sourceMappingURL=frontend.js.map