matterbridge 3.0.3 → 3.0.4-dev-20250525-c88cf84

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 (164) hide show
  1. package/CHANGELOG.md +25 -4
  2. package/README.md +6 -12
  3. package/dist/cli.js +2 -37
  4. package/dist/cluster/export.js +0 -2
  5. package/dist/defaultConfigSchema.js +0 -23
  6. package/dist/deviceManager.js +1 -94
  7. package/dist/frontend.js +100 -437
  8. package/dist/helpers.js +9 -53
  9. package/dist/index.js +1 -27
  10. package/dist/logger/export.js +0 -1
  11. package/dist/matter/behaviors.js +0 -2
  12. package/dist/matter/clusters.js +0 -2
  13. package/dist/matter/devices.js +0 -2
  14. package/dist/matter/endpoints.js +0 -2
  15. package/dist/matter/export.js +1 -3
  16. package/dist/matter/types.js +0 -2
  17. package/dist/matterbridge.js +47 -747
  18. package/dist/matterbridgeAccessoryPlatform.js +0 -34
  19. package/dist/matterbridgeBehaviors.js +43 -61
  20. package/dist/matterbridgeDeviceTypes.js +15 -563
  21. package/dist/matterbridgeDynamicPlatform.js +0 -34
  22. package/dist/matterbridgeEndpoint.js +68 -837
  23. package/dist/matterbridgeEndpointHelpers.js +33 -204
  24. package/dist/matterbridgePlatform.js +7 -225
  25. package/dist/matterbridgeTypes.js +0 -24
  26. package/dist/pluginManager.js +3 -264
  27. package/dist/roboticVacuumCleaner.js +3 -78
  28. package/dist/shelly.js +7 -155
  29. package/dist/storage/export.js +0 -1
  30. package/dist/update.js +0 -53
  31. package/dist/utils/colorUtils.js +2 -205
  32. package/dist/utils/commandLine.js +0 -53
  33. package/dist/utils/copyDirectory.js +1 -37
  34. package/dist/utils/createZip.js +2 -42
  35. package/dist/utils/deepCopy.js +0 -38
  36. package/dist/utils/deepEqual.js +1 -71
  37. package/dist/utils/export.js +0 -1
  38. package/dist/utils/hex.js +0 -57
  39. package/dist/utils/isvalid.js +0 -100
  40. package/dist/utils/network.js +5 -76
  41. package/dist/utils/wait.js +9 -58
  42. package/dist/waterHeater.js +0 -52
  43. package/npm-shrinkwrap.json +10 -10
  44. package/package.json +2 -3
  45. package/dist/cli.d.ts +0 -29
  46. package/dist/cli.d.ts.map +0 -1
  47. package/dist/cli.js.map +0 -1
  48. package/dist/cluster/export.d.ts +0 -2
  49. package/dist/cluster/export.d.ts.map +0 -1
  50. package/dist/cluster/export.js.map +0 -1
  51. package/dist/defaultConfigSchema.d.ts +0 -27
  52. package/dist/defaultConfigSchema.d.ts.map +0 -1
  53. package/dist/defaultConfigSchema.js.map +0 -1
  54. package/dist/deviceManager.d.ts +0 -114
  55. package/dist/deviceManager.d.ts.map +0 -1
  56. package/dist/deviceManager.js.map +0 -1
  57. package/dist/frontend.d.ts +0 -241
  58. package/dist/frontend.d.ts.map +0 -1
  59. package/dist/frontend.js.map +0 -1
  60. package/dist/helpers.d.ts +0 -47
  61. package/dist/helpers.d.ts.map +0 -1
  62. package/dist/helpers.js.map +0 -1
  63. package/dist/index.d.ts +0 -35
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/index.js.map +0 -1
  66. package/dist/logger/export.d.ts +0 -2
  67. package/dist/logger/export.d.ts.map +0 -1
  68. package/dist/logger/export.js.map +0 -1
  69. package/dist/matter/behaviors.d.ts +0 -2
  70. package/dist/matter/behaviors.d.ts.map +0 -1
  71. package/dist/matter/behaviors.js.map +0 -1
  72. package/dist/matter/clusters.d.ts +0 -2
  73. package/dist/matter/clusters.d.ts.map +0 -1
  74. package/dist/matter/clusters.js.map +0 -1
  75. package/dist/matter/devices.d.ts +0 -2
  76. package/dist/matter/devices.d.ts.map +0 -1
  77. package/dist/matter/devices.js.map +0 -1
  78. package/dist/matter/endpoints.d.ts +0 -2
  79. package/dist/matter/endpoints.d.ts.map +0 -1
  80. package/dist/matter/endpoints.js.map +0 -1
  81. package/dist/matter/export.d.ts +0 -5
  82. package/dist/matter/export.d.ts.map +0 -1
  83. package/dist/matter/export.js.map +0 -1
  84. package/dist/matter/types.d.ts +0 -3
  85. package/dist/matter/types.d.ts.map +0 -1
  86. package/dist/matter/types.js.map +0 -1
  87. package/dist/matterbridge.d.ts +0 -445
  88. package/dist/matterbridge.d.ts.map +0 -1
  89. package/dist/matterbridge.js.map +0 -1
  90. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -40
  91. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  92. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  93. package/dist/matterbridgeBehaviors.d.ts +0 -1201
  94. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  95. package/dist/matterbridgeBehaviors.js.map +0 -1
  96. package/dist/matterbridgeDeviceTypes.d.ts +0 -629
  97. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  98. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  99. package/dist/matterbridgeDynamicPlatform.d.ts +0 -40
  100. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  101. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  102. package/dist/matterbridgeEndpoint.d.ts +0 -967
  103. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  104. package/dist/matterbridgeEndpoint.js.map +0 -1
  105. package/dist/matterbridgeEndpointHelpers.d.ts +0 -2728
  106. package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
  107. package/dist/matterbridgeEndpointHelpers.js.map +0 -1
  108. package/dist/matterbridgePlatform.d.ts +0 -294
  109. package/dist/matterbridgePlatform.d.ts.map +0 -1
  110. package/dist/matterbridgePlatform.js.map +0 -1
  111. package/dist/matterbridgeTypes.d.ts +0 -188
  112. package/dist/matterbridgeTypes.d.ts.map +0 -1
  113. package/dist/matterbridgeTypes.js.map +0 -1
  114. package/dist/pluginManager.d.ts +0 -273
  115. package/dist/pluginManager.d.ts.map +0 -1
  116. package/dist/pluginManager.js.map +0 -1
  117. package/dist/roboticVacuumCleaner.d.ts +0 -82
  118. package/dist/roboticVacuumCleaner.d.ts.map +0 -1
  119. package/dist/roboticVacuumCleaner.js.map +0 -1
  120. package/dist/shelly.d.ts +0 -153
  121. package/dist/shelly.d.ts.map +0 -1
  122. package/dist/shelly.js.map +0 -1
  123. package/dist/storage/export.d.ts +0 -2
  124. package/dist/storage/export.d.ts.map +0 -1
  125. package/dist/storage/export.js.map +0 -1
  126. package/dist/update.d.ts +0 -58
  127. package/dist/update.d.ts.map +0 -1
  128. package/dist/update.js.map +0 -1
  129. package/dist/utils/colorUtils.d.ts +0 -61
  130. package/dist/utils/colorUtils.d.ts.map +0 -1
  131. package/dist/utils/colorUtils.js.map +0 -1
  132. package/dist/utils/commandLine.d.ts +0 -58
  133. package/dist/utils/commandLine.d.ts.map +0 -1
  134. package/dist/utils/commandLine.js.map +0 -1
  135. package/dist/utils/copyDirectory.d.ts +0 -32
  136. package/dist/utils/copyDirectory.d.ts.map +0 -1
  137. package/dist/utils/copyDirectory.js.map +0 -1
  138. package/dist/utils/createZip.d.ts +0 -38
  139. package/dist/utils/createZip.d.ts.map +0 -1
  140. package/dist/utils/createZip.js.map +0 -1
  141. package/dist/utils/deepCopy.d.ts +0 -31
  142. package/dist/utils/deepCopy.d.ts.map +0 -1
  143. package/dist/utils/deepCopy.js.map +0 -1
  144. package/dist/utils/deepEqual.d.ts +0 -53
  145. package/dist/utils/deepEqual.d.ts.map +0 -1
  146. package/dist/utils/deepEqual.js.map +0 -1
  147. package/dist/utils/export.d.ts +0 -11
  148. package/dist/utils/export.d.ts.map +0 -1
  149. package/dist/utils/export.js.map +0 -1
  150. package/dist/utils/hex.d.ts +0 -48
  151. package/dist/utils/hex.d.ts.map +0 -1
  152. package/dist/utils/hex.js.map +0 -1
  153. package/dist/utils/isvalid.d.ts +0 -102
  154. package/dist/utils/isvalid.d.ts.map +0 -1
  155. package/dist/utils/isvalid.js.map +0 -1
  156. package/dist/utils/network.d.ts +0 -69
  157. package/dist/utils/network.d.ts.map +0 -1
  158. package/dist/utils/network.js.map +0 -1
  159. package/dist/utils/wait.d.ts +0 -52
  160. package/dist/utils/wait.d.ts.map +0 -1
  161. package/dist/utils/wait.js.map +0 -1
  162. package/dist/waterHeater.d.ts +0 -75
  163. package/dist/waterHeater.d.ts.map +0 -1
  164. package/dist/waterHeater.js.map +0 -1
package/dist/frontend.js CHANGED
@@ -1,111 +1,29 @@
1
- /**
2
- * This file contains the class Frontend.
3
- *
4
- * @file frontend.ts
5
- * @author Luca Liguori
6
- * @date 2025-01-13
7
- * @version 1.0.2
8
- *
9
- * Copyright 2025, 2026, 2027 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
- // @matter
24
- import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
25
- // Node modules
1
+ import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
2
+ import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
26
3
  import { createServer } from 'node:http';
27
4
  import https from 'node:https';
28
5
  import os from 'node:os';
29
6
  import path from 'node:path';
30
7
  import { promises as fs } from 'node:fs';
31
- // Third-party modules
32
8
  import express from 'express';
33
9
  import WebSocket, { WebSocketServer } from 'ws';
34
10
  import multer from 'multer';
35
- // AnsiLogger module
36
11
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
37
- // Matterbridge
38
12
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout } from './utils/export.js';
39
13
  import { plg } from './matterbridgeTypes.js';
40
14
  import { hasParameter } from './utils/export.js';
41
- import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
42
- /**
43
- * Websocket message ID for logging.
44
- * @constant {number}
45
- */
15
+ import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
46
16
  export const WS_ID_LOG = 0;
47
- /**
48
- * Websocket message ID indicating a refresh is needed.
49
- * @constant {number}
50
- */
51
17
  export const WS_ID_REFRESH_NEEDED = 1;
52
- /**
53
- * Websocket message ID indicating a restart is needed.
54
- * @constant {number}
55
- */
56
18
  export const WS_ID_RESTART_NEEDED = 2;
57
- /**
58
- * Websocket message ID indicating a cpu update.
59
- * @constant {number}
60
- */
61
19
  export const WS_ID_CPU_UPDATE = 3;
62
- /**
63
- * Websocket message ID indicating a memory update.
64
- * @constant {number}
65
- */
66
20
  export const WS_ID_MEMORY_UPDATE = 4;
67
- /**
68
- * Websocket message ID indicating an uptime update.
69
- * @constant {number}
70
- */
71
21
  export const WS_ID_UPTIME_UPDATE = 5;
72
- /**
73
- * Websocket message ID indicating a snackbar message.
74
- * @constant {number}
75
- */
76
22
  export const WS_ID_SNACKBAR = 6;
77
- /**
78
- * Websocket message ID indicating matterbridge has un update available.
79
- * @constant {number}
80
- */
81
23
  export const WS_ID_UPDATE_NEEDED = 7;
82
- /**
83
- * Websocket message ID indicating a state update.
84
- * @constant {number}
85
- */
86
24
  export const WS_ID_STATEUPDATE = 8;
87
- /**
88
- * Websocket message ID indicating to close a permanent snackbar message.
89
- * @constant {number}
90
- */
91
25
  export const WS_ID_CLOSE_SNACKBAR = 9;
92
- /**
93
- * Websocket message ID indicating a shelly system update.
94
- * check:
95
- * curl -k http://127.0.0.1:8101/api/updates/sys/check
96
- * perform:
97
- * curl -k http://127.0.0.1:8101/api/updates/sys/perform
98
- * @constant {number}
99
- */
100
26
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
101
- /**
102
- * Websocket message ID indicating a shelly main update.
103
- * check:
104
- * curl -k http://127.0.0.1:8101/api/updates/main/check
105
- * perform:
106
- * curl -k http://127.0.0.1:8101/api/updates/main/perform
107
- * @constant {number}
108
- */
109
27
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
110
28
  export class Frontend {
111
29
  matterbridge;
@@ -118,7 +36,7 @@ export class Frontend {
118
36
  webSocketServer;
119
37
  constructor(matterbridge) {
120
38
  this.matterbridge = matterbridge;
121
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
39
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
122
40
  }
123
41
  set logLevel(logLevel) {
124
42
  this.log.logLevel = logLevel;
@@ -126,43 +44,13 @@ export class Frontend {
126
44
  async start(port = 8283) {
127
45
  this.port = port;
128
46
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
129
- // Initialize multer with the upload directory
130
47
  const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
131
48
  await fs.mkdir(uploadDir, { recursive: true });
132
49
  const upload = multer({ dest: uploadDir });
133
- // Create the express app that serves the frontend
134
50
  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
161
51
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
162
52
  if (!hasParameter('ssl')) {
163
- // Create an HTTP server and attach the express app
164
53
  this.httpServer = createServer(this.expressApp);
165
- // Listen on the specified port
166
54
  if (hasParameter('ingress')) {
167
55
  this.httpServer.listen(this.port, '0.0.0.0', () => {
168
56
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -176,7 +64,6 @@ export class Frontend {
176
64
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
177
65
  });
178
66
  }
179
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
67
  this.httpServer.on('error', (error) => {
181
68
  this.log.error(`Frontend http server error listening on ${this.port}`);
182
69
  switch (error.code) {
@@ -192,7 +79,6 @@ export class Frontend {
192
79
  });
193
80
  }
194
81
  else {
195
- // Load the SSL certificate, the private key and optionally the CA certificate
196
82
  let cert;
197
83
  try {
198
84
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -220,9 +106,7 @@ export class Frontend {
220
106
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
221
107
  }
222
108
  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
224
109
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
225
- // Listen on the specified port
226
110
  if (hasParameter('ingress')) {
227
111
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
228
112
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -236,7 +120,6 @@ export class Frontend {
236
120
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
237
121
  });
238
122
  }
239
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
123
  this.httpsServer.on('error', (error) => {
241
124
  this.log.error(`Frontend https server error listening on ${this.port}`);
242
125
  switch (error.code) {
@@ -253,18 +136,16 @@ export class Frontend {
253
136
  }
254
137
  if (this.initializeError)
255
138
  return;
256
- // Create a WebSocket server and attach it to the http or https server
257
139
  const wssPort = this.port;
258
140
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
259
141
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
260
142
  this.webSocketServer.on('connection', (ws, request) => {
261
143
  const clientIp = request.socket.remoteAddress;
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 */;
144
+ let callbackLogLevel = "notice";
145
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
146
+ callbackLogLevel = "info";
147
+ if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
148
+ callbackLogLevel = "debug";
268
149
  AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
269
150
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
270
151
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
@@ -298,7 +179,6 @@ export class Frontend {
298
179
  this.webSocketServer.on('error', (ws, error) => {
299
180
  this.log.error(`WebSocketServer error: ${error}`);
300
181
  });
301
- // Subscribe to cli events
302
182
  const { cliEmitter } = await import('./cli.js');
303
183
  cliEmitter.removeAllListeners();
304
184
  cliEmitter.on('uptime', (systemUptime, processUptime) => {
@@ -310,7 +190,6 @@ export class Frontend {
310
190
  cliEmitter.on('cpu', (cpuUsage) => {
311
191
  this.wssSendCpuUpdate(cpuUsage);
312
192
  });
313
- // Endpoint to validate login code
314
193
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
315
194
  const { password } = req.body;
316
195
  this.log.debug('The frontend sent /api/login', password);
@@ -329,27 +208,23 @@ export class Frontend {
329
208
  this.log.warn('/api/login error wrong password');
330
209
  res.json({ valid: false });
331
210
  }
332
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
333
211
  }
334
212
  catch (error) {
335
213
  this.log.error('/api/login error getting password');
336
214
  res.json({ valid: false });
337
215
  }
338
216
  });
339
- // Endpoint to provide health check for docker
340
217
  this.expressApp.get('/health', (req, res) => {
341
218
  this.log.debug('Express received /health');
342
219
  const healthStatus = {
343
- status: 'ok', // Indicate service is healthy
344
- uptime: process.uptime(), // Server uptime in seconds
345
- timestamp: new Date().toISOString(), // Current timestamp
220
+ status: 'ok',
221
+ uptime: process.uptime(),
222
+ timestamp: new Date().toISOString(),
346
223
  };
347
224
  res.status(200).json(healthStatus);
348
225
  });
349
- // Endpoint to provide memory usage details
350
226
  this.expressApp.get('/memory', async (req, res) => {
351
227
  this.log.debug('Express received /memory');
352
- // Memory usage from process
353
228
  const memoryUsageRaw = process.memoryUsage();
354
229
  const memoryUsage = {
355
230
  rss: this.formatMemoryUsage(memoryUsageRaw.rss),
@@ -358,13 +233,10 @@ export class Frontend {
358
233
  external: this.formatMemoryUsage(memoryUsageRaw.external),
359
234
  arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
360
235
  };
361
- // V8 heap statistics
362
236
  const { default: v8 } = await import('node:v8');
363
237
  const heapStatsRaw = v8.getHeapStatistics();
364
238
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
365
- // Format heapStats
366
239
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
367
- // Format heapSpaces
368
240
  const heapSpaces = heapSpacesRaw.map((space) => ({
369
241
  ...space,
370
242
  space_size: this.formatMemoryUsage(space.space_size),
@@ -382,23 +254,19 @@ export class Frontend {
382
254
  };
383
255
  res.status(200).json(memoryReport);
384
256
  });
385
- // Endpoint to provide settings
386
257
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
387
258
  this.log.debug('The frontend sent /api/settings');
388
259
  res.json(await this.getApiSettings());
389
260
  });
390
- // Endpoint to provide plugins
391
261
  this.expressApp.get('/api/plugins', async (req, res) => {
392
262
  this.log.debug('The frontend sent /api/plugins');
393
263
  res.json(this.getBaseRegisteredPlugins());
394
264
  });
395
- // Endpoint to provide devices
396
265
  this.expressApp.get('/api/devices', async (req, res) => {
397
266
  this.log.debug('The frontend sent /api/devices');
398
267
  const devices = await this.getDevices();
399
268
  res.json(devices);
400
269
  });
401
- // Endpoint to view the matterbridge log
402
270
  this.expressApp.get('/api/view-mblog', async (req, res) => {
403
271
  this.log.debug('The frontend sent /api/view-mblog');
404
272
  try {
@@ -411,7 +279,6 @@ export class Frontend {
411
279
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
412
280
  }
413
281
  });
414
- // Endpoint to view the matter.js log
415
282
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
416
283
  this.log.debug('The frontend sent /api/view-mjlog');
417
284
  try {
@@ -424,7 +291,6 @@ export class Frontend {
424
291
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
425
292
  }
426
293
  });
427
- // Endpoint to view the shelly log
428
294
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
429
295
  this.log.debug('The frontend sent /api/shellyviewsystemlog');
430
296
  try {
@@ -437,7 +303,6 @@ export class Frontend {
437
303
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
438
304
  }
439
305
  });
440
- // Endpoint to download the matterbridge log
441
306
  this.expressApp.get('/api/download-mblog', async (req, res) => {
442
307
  this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
443
308
  try {
@@ -450,7 +315,6 @@ export class Frontend {
450
315
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
451
316
  }
452
317
  res.type('text/plain');
453
- // res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
454
318
  res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
455
319
  if (error) {
456
320
  this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
@@ -458,7 +322,6 @@ export class Frontend {
458
322
  }
459
323
  });
460
324
  });
461
- // Endpoint to download the matter log
462
325
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
463
326
  this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
464
327
  try {
@@ -478,7 +341,6 @@ export class Frontend {
478
341
  }
479
342
  });
480
343
  });
481
- // Endpoint to download the shelly log
482
344
  this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
483
345
  this.log.debug('The frontend sent /api/shellydownloadsystemlog');
484
346
  try {
@@ -498,7 +360,6 @@ export class Frontend {
498
360
  }
499
361
  });
500
362
  });
501
- // Endpoint to download the matterbridge storage directory
502
363
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
503
364
  this.log.debug('The frontend sent /api/download-mbstorage');
504
365
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -509,7 +370,6 @@ export class Frontend {
509
370
  }
510
371
  });
511
372
  });
512
- // Endpoint to download the matter storage file
513
373
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
514
374
  this.log.debug('The frontend sent /api/download-mjstorage');
515
375
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -520,7 +380,6 @@ export class Frontend {
520
380
  }
521
381
  });
522
382
  });
523
- // Endpoint to download the matterbridge plugin directory
524
383
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
525
384
  this.log.debug('The frontend sent /api/download-pluginstorage');
526
385
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -531,7 +390,6 @@ export class Frontend {
531
390
  }
532
391
  });
533
392
  });
534
- // Endpoint to download the matterbridge plugin config files
535
393
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
536
394
  this.log.debug('The frontend sent /api/download-pluginconfig');
537
395
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
@@ -542,7 +400,6 @@ export class Frontend {
542
400
  }
543
401
  });
544
402
  });
545
- // Endpoint to download the matterbridge backup (created with the backup command)
546
403
  this.expressApp.get('/api/download-backup', async (req, res) => {
547
404
  this.log.debug('The frontend sent /api/download-backup');
548
405
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -552,7 +409,6 @@ export class Frontend {
552
409
  }
553
410
  });
554
411
  });
555
- // Endpoint to upload a package
556
412
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
557
413
  const { filename } = req.body;
558
414
  const file = req.file;
@@ -562,13 +418,10 @@ export class Frontend {
562
418
  return;
563
419
  }
564
420
  this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
565
- // Define the path where the plugin file will be saved
566
421
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
567
422
  try {
568
- // Move the uploaded file to the specified path
569
423
  await fs.rename(file.path, filePath);
570
424
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
571
- // Install the plugin package
572
425
  if (filename.endsWith('.tgz')) {
573
426
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
574
427
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
@@ -587,7 +440,6 @@ export class Frontend {
587
440
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
588
441
  }
589
442
  });
590
- // Fallback for routing (must be the last route)
591
443
  this.expressApp.use((req, res) => {
592
444
  this.log.debug('The frontend sent:', req.url);
593
445
  res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -596,15 +448,12 @@ export class Frontend {
596
448
  }
597
449
  async stop() {
598
450
  this.log.debug('Stopping the frontend...');
599
- // Remove listeners from the express app
600
451
  if (this.expressApp) {
601
452
  this.expressApp.removeAllListeners();
602
453
  this.expressApp = undefined;
603
454
  this.log.debug('Frontend app closed successfully');
604
455
  }
605
- // Close the WebSocket server
606
456
  if (this.webSocketServer) {
607
- // Close all active connections
608
457
  this.webSocketServer.clients.forEach((client) => {
609
458
  if (client.readyState === WebSocket.OPEN) {
610
459
  client.close();
@@ -624,7 +473,6 @@ export class Frontend {
624
473
  this.webSocketServer.removeAllListeners();
625
474
  this.webSocketServer = undefined;
626
475
  }
627
- // Close the http server
628
476
  if (this.httpServer) {
629
477
  await withTimeout(new Promise((resolve) => {
630
478
  this.httpServer?.close((error) => {
@@ -641,7 +489,6 @@ export class Frontend {
641
489
  this.httpServer = undefined;
642
490
  this.log.debug('Frontend http server closed successfully');
643
491
  }
644
- // Close the https server
645
492
  if (this.httpsServer) {
646
493
  await withTimeout(new Promise((resolve) => {
647
494
  this.httpsServer?.close((error) => {
@@ -660,7 +507,6 @@ export class Frontend {
660
507
  }
661
508
  this.log.debug('Frontend stopped successfully');
662
509
  }
663
- // Function to format bytes to KB, MB, or GB
664
510
  formatMemoryUsage = (bytes) => {
665
511
  if (bytes >= 1024 ** 3) {
666
512
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
@@ -672,7 +518,6 @@ export class Frontend {
672
518
  return `${(bytes / 1024).toFixed(2)} KB`;
673
519
  }
674
520
  };
675
- // Function to format system uptime with only the most significant unit
676
521
  formatOsUpTime = (seconds) => {
677
522
  if (seconds >= 86400) {
678
523
  const days = Math.floor(seconds / 86400);
@@ -688,14 +533,8 @@ export class Frontend {
688
533
  }
689
534
  return `${seconds} second${seconds !== 1 ? 's' : ''}`;
690
535
  };
691
- /**
692
- * Retrieves the api settings data.
693
- *
694
- * @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
695
- */
696
536
  async getApiSettings() {
697
537
  const { lastCpuUsage } = await import('./cli.js');
698
- // Update the system information
699
538
  this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
700
539
  this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
701
540
  this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
@@ -704,7 +543,6 @@ export class Frontend {
704
543
  this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
705
544
  this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
706
545
  this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
707
- // Update the matterbridge information
708
546
  this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
709
547
  this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
710
548
  this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
@@ -723,11 +561,6 @@ export class Frontend {
723
561
  this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
724
562
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
725
563
  }
726
- /**
727
- * Retrieves the reachable attribute.
728
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
729
- * @returns {boolean} The reachable attribute.
730
- */
731
564
  getReachability(device) {
732
565
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
733
566
  return false;
@@ -737,8 +570,8 @@ export class Frontend {
737
570
  return true;
738
571
  return false;
739
572
  }
740
- getPowerSource(device) {
741
- if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
573
+ getPowerSource(endpoint) {
574
+ if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
742
575
  return undefined;
743
576
  const powerSource = (device) => {
744
577
  const featureMap = device.getAttribute(PowerSource.Cluster.id, 'featureMap');
@@ -752,20 +585,13 @@ export class Frontend {
752
585
  }
753
586
  return;
754
587
  };
755
- // Root endpoint
756
- if (device.hasClusterServer(PowerSource.Cluster.id))
757
- return powerSource(device);
758
- // Child endpoints
759
- for (const child of device.getChildEndpoints()) {
588
+ if (endpoint.hasClusterServer(PowerSource.Cluster.id))
589
+ return powerSource(endpoint);
590
+ for (const child of endpoint.getChildEndpoints()) {
760
591
  if (child.hasClusterServer(PowerSource.Cluster.id))
761
592
  return powerSource(child);
762
593
  }
763
594
  }
764
- /**
765
- * Retrieves the cluster text description from a given device.
766
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
767
- * @returns {string} The attributes description of the cluster servers in the device.
768
- */
769
595
  getClusterTextFromDevice(device) {
770
596
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
771
597
  return '';
@@ -807,8 +633,7 @@ export class Frontend {
807
633
  let attributes = '';
808
634
  let supportedModes = [];
809
635
  device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
810
- // console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
811
- if (typeof attributeValue === 'undefined')
636
+ if (typeof attributeValue === 'undefined' || attributeValue === undefined)
812
637
  return;
813
638
  if (clusterName === 'onOff' && attributeName === 'onOff')
814
639
  attributes += `OnOff: ${attributeValue} `;
@@ -899,13 +724,8 @@ export class Frontend {
899
724
  if (clusterName === 'userLabel' && attributeName === 'labelList')
900
725
  attributes += `${getUserLabel(device)} `;
901
726
  });
902
- // console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
903
727
  return attributes.trimStart().trimEnd();
904
728
  }
905
- /**
906
- * Retrieves the base registered plugins sanitized for res.json().
907
- * @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
908
- */
909
729
  getBaseRegisteredPlugins() {
910
730
  const baseRegisteredPlugins = [];
911
731
  for (const plugin of this.matterbridge.plugins) {
@@ -944,18 +764,11 @@ export class Frontend {
944
764
  }
945
765
  return baseRegisteredPlugins;
946
766
  }
947
- /**
948
- * Retrieves the devices from Matterbridge.
949
- * @param {string} [pluginName] - The name of the plugin to filter devices by.
950
- * @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
951
- */
952
767
  async getDevices(pluginName) {
953
768
  const devices = [];
954
769
  this.matterbridge.devices.forEach(async (device) => {
955
- // Filter by pluginName if provided
956
770
  if (pluginName && pluginName !== device.plugin)
957
771
  return;
958
- // Check if the device has the required properties
959
772
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
960
773
  return;
961
774
  const cluster = this.getClusterTextFromDevice(device);
@@ -975,13 +788,64 @@ export class Frontend {
975
788
  });
976
789
  return devices;
977
790
  }
978
- /**
979
- * Handles incoming websocket messages for the Matterbridge frontend.
980
- *
981
- * @param {WebSocket} client - The websocket client that sent the message.
982
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
983
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
984
- */
791
+ getClusters(pluginName, endpointNumber) {
792
+ const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
793
+ if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
794
+ this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
795
+ return;
796
+ }
797
+ const deviceTypes = [];
798
+ const clusters = [];
799
+ endpoint.state.descriptor.deviceTypeList.forEach((d) => {
800
+ deviceTypes.push(d.deviceType);
801
+ });
802
+ endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
803
+ if (typeof attributeValue === 'undefined' || attributeValue === undefined)
804
+ return;
805
+ if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
806
+ return;
807
+ clusters.push({
808
+ endpoint: endpoint.number.toString(),
809
+ id: 'main',
810
+ deviceTypes,
811
+ clusterName: capitalizeFirstLetter(clusterName),
812
+ clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
813
+ attributeName,
814
+ attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
815
+ attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
816
+ attributeLocalValue: attributeValue,
817
+ });
818
+ });
819
+ const childEndpoints = endpoint.getChildEndpoints();
820
+ childEndpoints.forEach((childEndpoint) => {
821
+ if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
822
+ this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
823
+ return;
824
+ }
825
+ const deviceTypes = [];
826
+ childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
827
+ deviceTypes.push(d.deviceType);
828
+ });
829
+ childEndpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
830
+ if (typeof attributeValue === 'undefined' || attributeValue === undefined)
831
+ return;
832
+ if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
833
+ return;
834
+ clusters.push({
835
+ endpoint: childEndpoint.number.toString(),
836
+ id: childEndpoint.maybeId ?? 'null',
837
+ deviceTypes,
838
+ clusterName: capitalizeFirstLetter(clusterName),
839
+ clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
840
+ attributeName,
841
+ attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
842
+ attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
843
+ attributeLocalValue: attributeValue,
844
+ });
845
+ });
846
+ });
847
+ return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
848
+ }
985
849
  async wsMessageHandler(client, message) {
986
850
  let data;
987
851
  try {
@@ -1028,10 +892,8 @@ export class Frontend {
1028
892
  this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
1029
893
  const packageName = data.params.packageName.replace(/@.*$/, '');
1030
894
  if (data.params.restart === false && packageName !== 'matterbridge') {
1031
- // The install comes from InstallPlugins
1032
895
  this.matterbridge.plugins.add(packageName).then((plugin) => {
1033
896
  if (plugin) {
1034
- // The plugin is not registered
1035
897
  this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
1036
898
  this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
1037
899
  this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
@@ -1039,7 +901,6 @@ export class Frontend {
1039
901
  });
1040
902
  }
1041
903
  else {
1042
- // The plugin is already registered
1043
904
  this.wssSendSnackbarMessage(`Restart required`, 0);
1044
905
  this.wssSendRefreshRequired('plugins');
1045
906
  this.wssSendRestartRequired();
@@ -1047,7 +908,6 @@ export class Frontend {
1047
908
  });
1048
909
  }
1049
910
  else {
1050
- // The package is matterbridge
1051
911
  if (this.matterbridge.restartMode !== '') {
1052
912
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1053
913
  this.matterbridge.shutdownProcess();
@@ -1069,7 +929,6 @@ export class Frontend {
1069
929
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1070
930
  return;
1071
931
  }
1072
- // The package is a plugin
1073
932
  const plugin = this.matterbridge.plugins.get(data.params.packageName);
1074
933
  if (plugin) {
1075
934
  await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
@@ -1078,7 +937,6 @@ export class Frontend {
1078
937
  this.wssSendRefreshRequired('plugins');
1079
938
  this.wssSendRefreshRequired('devices');
1080
939
  }
1081
- // Uninstall the package
1082
940
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1083
941
  this.matterbridge
1084
942
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
@@ -1300,103 +1158,24 @@ export class Frontend {
1300
1158
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter endpoint in /api/clusters' }));
1301
1159
  return;
1302
1160
  }
1303
- const clusters = [];
1304
- let deviceName = '';
1305
- let serialNumber = '';
1306
- let deviceTypes = [];
1307
- this.matterbridge.devices.forEach(async (device) => {
1308
- if (data.params.plugin !== device.plugin)
1309
- return;
1310
- if (data.params.endpoint !== device.number)
1311
- return;
1312
- deviceName = device.deviceName ?? 'Unknown';
1313
- serialNumber = device.serialNumber ?? 'Unknown';
1314
- deviceTypes = [];
1315
- const endpointServer = EndpointServer.forEndpoint(device);
1316
- const clusterServers = endpointServer.getAllClusterServers();
1317
- clusterServers.forEach((clusterServer) => {
1318
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
1319
- if (clusterServer.name === 'EveHistory') {
1320
- if (['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(key)) {
1321
- return;
1322
- }
1323
- }
1324
- if (clusterServer.name === 'Descriptor' && key === 'deviceTypeList') {
1325
- value.getLocal().forEach((deviceType) => {
1326
- deviceTypes.push(deviceType.deviceType);
1327
- });
1328
- }
1329
- let attributeValue;
1330
- let attributeLocalValue;
1331
- try {
1332
- if (typeof value.getLocal() === 'object')
1333
- attributeValue = stringify(value.getLocal());
1334
- else
1335
- attributeValue = value.getLocal().toString();
1336
- attributeLocalValue = value.getLocal();
1337
- }
1338
- catch (error) {
1339
- attributeValue = 'Fabric-Scoped';
1340
- attributeLocalValue = 'Fabric-Scoped';
1341
- this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
1342
- }
1343
- clusters.push({
1344
- endpoint: device.number ? device.number.toString() : '...',
1345
- id: 'main',
1346
- deviceTypes,
1347
- clusterName: clusterServer.name,
1348
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
1349
- attributeName: key,
1350
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
1351
- attributeValue,
1352
- attributeLocalValue,
1353
- });
1354
- });
1355
- });
1356
- endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1357
- deviceTypes = [];
1358
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1359
- const name = childEndpoint.endpoint?.id;
1360
- const clusterServers = childEndpoint.getAllClusterServers();
1361
- clusterServers.forEach((clusterServer) => {
1362
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
1363
- if (clusterServer.name === 'EveHistory')
1364
- return;
1365
- if (clusterServer.name === 'Descriptor' && key === 'deviceTypeList') {
1366
- value.getLocal().forEach((deviceType) => {
1367
- deviceTypes.push(deviceType.deviceType);
1368
- });
1369
- }
1370
- let attributeValue;
1371
- let attributeLocalValue;
1372
- try {
1373
- if (typeof value.getLocal() === 'object')
1374
- attributeValue = stringify(value.getLocal());
1375
- else
1376
- attributeValue = value.getLocal().toString();
1377
- attributeLocalValue = value.getLocal();
1378
- }
1379
- catch (error) {
1380
- attributeValue = 'Fabric-Scoped';
1381
- attributeLocalValue = 'Fabric-Scoped';
1382
- this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
1383
- }
1384
- clusters.push({
1385
- endpoint: childEndpoint.number ? childEndpoint.number.toString() : '...',
1386
- id: name,
1387
- deviceTypes,
1388
- clusterName: clusterServer.name,
1389
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
1390
- attributeName: key,
1391
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
1392
- attributeValue,
1393
- attributeLocalValue,
1394
- });
1395
- });
1396
- });
1397
- });
1398
- });
1399
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, deviceName, serialNumber, endpoint: data.params.endpoint, deviceTypes, response: clusters }));
1161
+ const response = this.getClusters(data.params.plugin, data.params.endpoint);
1162
+ if (response) {
1163
+ client.send(JSON.stringify({
1164
+ id: data.id,
1165
+ method: data.method,
1166
+ src: 'Matterbridge',
1167
+ dst: data.src,
1168
+ plugin: data.params.plugin,
1169
+ deviceName: response.deviceName,
1170
+ serialNumber: response.serialNumber,
1171
+ endpoint: response.endpoint,
1172
+ deviceTypes: response.deviceTypes,
1173
+ response: response.clusters,
1174
+ }));
1175
+ }
1176
+ else {
1177
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Endpoint not found in /api/clusters' }));
1178
+ }
1400
1179
  }
1401
1180
  else if (data.method === '/api/select' || data.method === '/api/select/devices') {
1402
1181
  if (!isValidString(data.params.plugin, 10)) {
@@ -1469,22 +1248,22 @@ export class Frontend {
1469
1248
  if (isValidString(data.params.value, 4)) {
1470
1249
  this.log.debug('Matterbridge logger level:', data.params.value);
1471
1250
  if (data.params.value === 'Debug') {
1472
- await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
1251
+ await this.matterbridge.setLogLevel("debug");
1473
1252
  }
1474
1253
  else if (data.params.value === 'Info') {
1475
- await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
1254
+ await this.matterbridge.setLogLevel("info");
1476
1255
  }
1477
1256
  else if (data.params.value === 'Notice') {
1478
- await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
1257
+ await this.matterbridge.setLogLevel("notice");
1479
1258
  }
1480
1259
  else if (data.params.value === 'Warn') {
1481
- await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
1260
+ await this.matterbridge.setLogLevel("warn");
1482
1261
  }
1483
1262
  else if (data.params.value === 'Error') {
1484
- await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
1263
+ await this.matterbridge.setLogLevel("error");
1485
1264
  }
1486
1265
  else if (data.params.value === 'Fatal') {
1487
- await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
1266
+ await this.matterbridge.setLogLevel("fatal");
1488
1267
  }
1489
1268
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1490
1269
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1495,7 +1274,6 @@ export class Frontend {
1495
1274
  this.log.debug('Matterbridge file log:', data.params.value);
1496
1275
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1497
1276
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1498
- // Create the file logger for matterbridge
1499
1277
  if (data.params.value)
1500
1278
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1501
1279
  else
@@ -1660,19 +1438,15 @@ export class Frontend {
1660
1438
  return;
1661
1439
  }
1662
1440
  const config = plugin.configJson;
1663
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1664
1441
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1665
- // this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1666
1442
  if (select === 'serial')
1667
1443
  this.log.info(`Selected device serial ${data.params.serial}`);
1668
1444
  if (select === 'name')
1669
1445
  this.log.info(`Selected device name ${data.params.name}`);
1670
1446
  if (config && select && (select === 'serial' || select === 'name')) {
1671
- // Remove postfix from the serial if it exists
1672
1447
  if (config.postfix) {
1673
1448
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1674
1449
  }
1675
- // Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
1676
1450
  if (isValidArray(config.whiteList, 1)) {
1677
1451
  if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
1678
1452
  config.whiteList.push(data.params.serial);
@@ -1681,7 +1455,6 @@ export class Frontend {
1681
1455
  config.whiteList.push(data.params.name);
1682
1456
  }
1683
1457
  }
1684
- // Remove the serial from the blackList if the blackList exists and the serial or name is in it
1685
1458
  if (isValidArray(config.blackList, 1)) {
1686
1459
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1687
1460
  config.blackList = config.blackList.filter((item) => item !== data.params.serial);
@@ -1711,9 +1484,7 @@ export class Frontend {
1711
1484
  return;
1712
1485
  }
1713
1486
  const config = plugin.configJson;
1714
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1715
1487
  const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
1716
- // this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
1717
1488
  if (select === 'serial')
1718
1489
  this.log.info(`Unselected device serial ${data.params.serial}`);
1719
1490
  if (select === 'name')
@@ -1722,7 +1493,6 @@ export class Frontend {
1722
1493
  if (config.postfix) {
1723
1494
  data.params.serial = data.params.serial.replace('-' + config.postfix, '');
1724
1495
  }
1725
- // Remove the serial from the whiteList if the whiteList exists and the serial is in it
1726
1496
  if (isValidArray(config.whiteList, 1)) {
1727
1497
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1728
1498
  config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
@@ -1731,7 +1501,6 @@ export class Frontend {
1731
1501
  config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1732
1502
  }
1733
1503
  }
1734
- // Add the serial to the blackList
1735
1504
  if (isValidArray(config.blackList)) {
1736
1505
  if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
1737
1506
  config.blackList.push(data.params.serial);
@@ -1764,219 +1533,114 @@ export class Frontend {
1764
1533
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1765
1534
  }
1766
1535
  }
1767
- /**
1768
- * Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1769
- *
1770
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1771
- * @param {string} time - The time string of the message
1772
- * @param {string} name - The logger name of the message
1773
- * @param {string} message - The content of the message.
1774
- *
1775
- * @remark
1776
- * The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
1777
- * It also replaces all occurrences of \" with " and angle-brackets with &lt; and &gt;.
1778
- * The function sends the message to all connected clients.
1779
- */
1780
1536
  wssSendMessage(level, time, name, message) {
1781
1537
  if (!level || !time || !name || !message)
1782
1538
  return;
1783
- // Remove ANSI escape codes from the message
1784
- // eslint-disable-next-line no-control-regex
1785
1539
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1786
- // Remove leading asterisks from the message
1787
1540
  message = message.replace(/^\*+/, '');
1788
- // Replace all occurrences of \t and \n
1789
1541
  message = message.replace(/[\t\n]/g, '');
1790
- // Remove non-printable characters
1791
- // eslint-disable-next-line no-control-regex
1792
1542
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1793
- // Replace all occurrences of \" with "
1794
1543
  message = message.replace(/\\"/g, '"');
1795
- // Replace all occurrences of angle-brackets with &lt; and &gt;"
1796
1544
  message = message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1797
- // Define the maximum allowed length for continuous characters without a space
1798
1545
  const maxContinuousLength = 100;
1799
1546
  const keepStartLength = 20;
1800
1547
  const keepEndLength = 20;
1801
- // Split the message into words
1802
1548
  message = message
1803
1549
  .split(' ')
1804
1550
  .map((word) => {
1805
- // If the word length exceeds the max continuous length, insert spaces and truncate
1806
1551
  if (word.length > maxContinuousLength) {
1807
1552
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1808
1553
  }
1809
1554
  return word;
1810
1555
  })
1811
1556
  .join(' ');
1812
- // Send the message to all connected clients
1813
1557
  this.webSocketServer?.clients.forEach((client) => {
1814
1558
  if (client.readyState === WebSocket.OPEN) {
1815
1559
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1816
1560
  }
1817
1561
  });
1818
1562
  }
1819
- /**
1820
- * Sends a need to refresh WebSocket message to all connected clients.
1821
- *
1822
- * @param {string} changed - The changed value. If null, the whole page will be refreshed.
1823
- * possible values:
1824
- * - 'matterbridgeLatestVersion'
1825
- * - 'matterbridgeAdvertise'
1826
- * - 'online'
1827
- * - 'offline'
1828
- * - 'reachability'
1829
- * - 'settings'
1830
- * - 'plugins'
1831
- * - 'pluginsRestart'
1832
- * - 'devices'
1833
- * - 'fabrics'
1834
- * - 'sessions'
1835
- */
1836
1563
  wssSendRefreshRequired(changed = null) {
1837
1564
  this.log.debug('Sending a refresh required message to all connected clients');
1838
- // Send the message to all connected clients
1839
1565
  this.webSocketServer?.clients.forEach((client) => {
1840
1566
  if (client.readyState === WebSocket.OPEN) {
1841
1567
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
1842
1568
  }
1843
1569
  });
1844
1570
  }
1845
- /**
1846
- * Sends a need to restart WebSocket message to all connected clients.
1847
- *
1848
- */
1849
1571
  wssSendRestartRequired(snackbar = true) {
1850
1572
  this.log.debug('Sending a restart required message to all connected clients');
1851
1573
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1852
1574
  if (snackbar === true)
1853
1575
  this.wssSendSnackbarMessage(`Restart required`, 0);
1854
- // Send the message to all connected clients
1855
1576
  this.webSocketServer?.clients.forEach((client) => {
1856
1577
  if (client.readyState === WebSocket.OPEN) {
1857
1578
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
1858
1579
  }
1859
1580
  });
1860
1581
  }
1861
- /**
1862
- * Sends a need to update WebSocket message to all connected clients.
1863
- *
1864
- */
1865
1582
  wssSendUpdateRequired() {
1866
1583
  this.log.debug('Sending an update required message to all connected clients');
1867
1584
  this.matterbridge.matterbridgeInformation.updateRequired = true;
1868
- // Send the message to all connected clients
1869
1585
  this.webSocketServer?.clients.forEach((client) => {
1870
1586
  if (client.readyState === WebSocket.OPEN) {
1871
1587
  client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
1872
1588
  }
1873
1589
  });
1874
1590
  }
1875
- /**
1876
- * Sends a cpu update message to all connected clients.
1877
- *
1878
- */
1879
1591
  wssSendCpuUpdate(cpuUsage) {
1880
1592
  if (hasParameter('debug'))
1881
1593
  this.log.debug('Sending a cpu update message to all connected clients');
1882
- // Send the message to all connected clients
1883
1594
  this.webSocketServer?.clients.forEach((client) => {
1884
1595
  if (client.readyState === WebSocket.OPEN) {
1885
1596
  client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
1886
1597
  }
1887
1598
  });
1888
1599
  }
1889
- /**
1890
- * Sends a memory update message to all connected clients.
1891
- *
1892
- */
1893
1600
  wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
1894
1601
  if (hasParameter('debug'))
1895
1602
  this.log.debug('Sending a memory update message to all connected clients');
1896
- // Send the message to all connected clients
1897
1603
  this.webSocketServer?.clients.forEach((client) => {
1898
1604
  if (client.readyState === WebSocket.OPEN) {
1899
1605
  client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
1900
1606
  }
1901
1607
  });
1902
1608
  }
1903
- /**
1904
- * Sends an uptime update message to all connected clients.
1905
- *
1906
- */
1907
1609
  wssSendUptimeUpdate(systemUptime, processUptime) {
1908
1610
  if (hasParameter('debug'))
1909
1611
  this.log.debug('Sending a uptime update message to all connected clients');
1910
- // Send the message to all connected clients
1911
1612
  this.webSocketServer?.clients.forEach((client) => {
1912
1613
  if (client.readyState === WebSocket.OPEN) {
1913
1614
  client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
1914
1615
  }
1915
1616
  });
1916
1617
  }
1917
- /**
1918
- * Sends an open snackbar message to all connected clients.
1919
- * @param {string} message - The message to send.
1920
- * @param {number} timeout - The timeout in seconds for the snackbar message.
1921
- * @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
1922
- *
1923
- */
1924
1618
  wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
1925
1619
  this.log.debug('Sending a snackbar message to all connected clients');
1926
- // Send the message to all connected clients
1927
1620
  this.webSocketServer?.clients.forEach((client) => {
1928
1621
  if (client.readyState === WebSocket.OPEN) {
1929
1622
  client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1930
1623
  }
1931
1624
  });
1932
1625
  }
1933
- /**
1934
- * Sends a close snackbar message to all connected clients.
1935
- * @param {string} message - The message to send.
1936
- *
1937
- */
1938
1626
  wssSendCloseSnackbarMessage(message) {
1939
1627
  this.log.debug('Sending a close snackbar message to all connected clients');
1940
- // Send the message to all connected clients
1941
1628
  this.webSocketServer?.clients.forEach((client) => {
1942
1629
  if (client.readyState === WebSocket.OPEN) {
1943
1630
  client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1944
1631
  }
1945
1632
  });
1946
1633
  }
1947
- /**
1948
- * Sends an attribute update message to all connected WebSocket clients.
1949
- *
1950
- * @param {string | undefined} plugin - The name of the plugin.
1951
- * @param {string | undefined} serialNumber - The serial number of the device.
1952
- * @param {string | undefined} uniqueId - The unique identifier of the device.
1953
- * @param {string} cluster - The cluster name where the attribute belongs.
1954
- * @param {string} attribute - The name of the attribute that changed.
1955
- * @param {number | string | boolean} value - The new value of the attribute.
1956
- *
1957
- * @remarks
1958
- * This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
1959
- * with the updated attribute information.
1960
- */
1961
1634
  wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1962
1635
  this.log.debug('Sending an attribute update message to all connected clients');
1963
- // Send the message to all connected clients
1964
1636
  this.webSocketServer?.clients.forEach((client) => {
1965
1637
  if (client.readyState === WebSocket.OPEN) {
1966
1638
  client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
1967
1639
  }
1968
1640
  });
1969
1641
  }
1970
- /**
1971
- * Sends a message to all connected clients.
1972
- * @param {number} id - The message id.
1973
- * @param {string} method - The message method.
1974
- * @param {Record<string, string | number | boolean>} params - The message parameters.
1975
- *
1976
- */
1977
1642
  wssBroadcastMessage(id, method, params) {
1978
1643
  this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
1979
- // Send the message to all connected clients
1980
1644
  this.webSocketServer?.clients.forEach((client) => {
1981
1645
  if (client.readyState === WebSocket.OPEN) {
1982
1646
  client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
@@ -1984,4 +1648,3 @@ export class Frontend {
1984
1648
  });
1985
1649
  }
1986
1650
  }
1987
- //# sourceMappingURL=frontend.js.map