matterbridge 2.0.0 → 2.1.0-dev.10

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 (103) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/README.md +1 -1
  3. package/dist/cli.js +0 -26
  4. package/dist/cluster/export.js +0 -2
  5. package/dist/defaultConfigSchema.js +0 -23
  6. package/dist/deviceManager.js +1 -26
  7. package/dist/frontend.js +143 -313
  8. package/dist/index.js +2 -30
  9. package/dist/logger/export.js +0 -1
  10. package/dist/matter/behaviors.js +1 -0
  11. package/dist/matter/clusters.js +1 -0
  12. package/dist/matter/devices.js +1 -0
  13. package/dist/matter/endpoints.js +1 -0
  14. package/dist/matter/export.js +1 -11
  15. package/dist/matter/types.js +2 -0
  16. package/dist/matterbridge.js +77 -715
  17. package/dist/matterbridgeAccessoryPlatform.js +0 -33
  18. package/dist/matterbridgeBehaviors.js +38 -38
  19. package/dist/matterbridgeDeviceTypes.js +11 -112
  20. package/dist/matterbridgeDynamicPlatform.js +0 -33
  21. package/dist/matterbridgeEndpoint.js +664 -2563
  22. package/dist/matterbridgeEndpointHelpers.js +513 -0
  23. package/dist/matterbridgePlatform.js +5 -125
  24. package/dist/matterbridgeTypes.js +0 -28
  25. package/dist/pluginManager.js +3 -240
  26. package/dist/storage/export.js +0 -1
  27. package/dist/utils/colorUtils.js +2 -205
  28. package/dist/utils/export.js +0 -1
  29. package/dist/utils/utils.js +7 -251
  30. package/frontend/build/asset-manifest.json +3 -3
  31. package/frontend/build/index.html +1 -1
  32. package/frontend/build/static/js/{main.6df4ebe4.js → main.26dbf9b9.js} +3 -3
  33. package/frontend/build/static/js/{main.6df4ebe4.js.map → main.26dbf9b9.js.map} +1 -1
  34. package/npm-shrinkwrap.json +86 -78
  35. package/package.json +22 -4
  36. package/dist/cli.d.ts +0 -25
  37. package/dist/cli.d.ts.map +0 -1
  38. package/dist/cli.js.map +0 -1
  39. package/dist/cluster/export.d.ts +0 -2
  40. package/dist/cluster/export.d.ts.map +0 -1
  41. package/dist/cluster/export.js.map +0 -1
  42. package/dist/defaultConfigSchema.d.ts +0 -27
  43. package/dist/defaultConfigSchema.d.ts.map +0 -1
  44. package/dist/defaultConfigSchema.js.map +0 -1
  45. package/dist/deviceManager.d.ts +0 -46
  46. package/dist/deviceManager.d.ts.map +0 -1
  47. package/dist/deviceManager.js.map +0 -1
  48. package/dist/frontend.d.ts +0 -98
  49. package/dist/frontend.d.ts.map +0 -1
  50. package/dist/frontend.js.map +0 -1
  51. package/dist/index.d.ts +0 -34
  52. package/dist/index.d.ts.map +0 -1
  53. package/dist/index.js.map +0 -1
  54. package/dist/logger/export.d.ts +0 -2
  55. package/dist/logger/export.d.ts.map +0 -1
  56. package/dist/logger/export.js.map +0 -1
  57. package/dist/matter/export.d.ts +0 -10
  58. package/dist/matter/export.d.ts.map +0 -1
  59. package/dist/matter/export.js.map +0 -1
  60. package/dist/matterbridge.d.ts +0 -356
  61. package/dist/matterbridge.d.ts.map +0 -1
  62. package/dist/matterbridge.js.map +0 -1
  63. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
  64. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  65. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  66. package/dist/matterbridgeBehaviors.d.ts +0 -963
  67. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  68. package/dist/matterbridgeBehaviors.js.map +0 -1
  69. package/dist/matterbridgeDeviceTypes.d.ts +0 -177
  70. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  71. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  72. package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
  73. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  74. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  75. package/dist/matterbridgeEndpoint.d.ts +0 -10254
  76. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  77. package/dist/matterbridgeEndpoint.js.map +0 -1
  78. package/dist/matterbridgeEndpointDefault.d.ts +0 -2
  79. package/dist/matterbridgeEndpointDefault.d.ts.map +0 -1
  80. package/dist/matterbridgeEndpointDefault.js +0 -159
  81. package/dist/matterbridgeEndpointDefault.js.map +0 -1
  82. package/dist/matterbridgePlatform.d.ts +0 -164
  83. package/dist/matterbridgePlatform.d.ts.map +0 -1
  84. package/dist/matterbridgePlatform.js.map +0 -1
  85. package/dist/matterbridgeTypes.d.ts +0 -167
  86. package/dist/matterbridgeTypes.d.ts.map +0 -1
  87. package/dist/matterbridgeTypes.js.map +0 -1
  88. package/dist/pluginManager.d.ts +0 -238
  89. package/dist/pluginManager.d.ts.map +0 -1
  90. package/dist/pluginManager.js.map +0 -1
  91. package/dist/storage/export.d.ts +0 -2
  92. package/dist/storage/export.d.ts.map +0 -1
  93. package/dist/storage/export.js.map +0 -1
  94. package/dist/utils/colorUtils.d.ts +0 -61
  95. package/dist/utils/colorUtils.d.ts.map +0 -1
  96. package/dist/utils/colorUtils.js.map +0 -1
  97. package/dist/utils/export.d.ts +0 -3
  98. package/dist/utils/export.d.ts.map +0 -1
  99. package/dist/utils/export.js.map +0 -1
  100. package/dist/utils/utils.d.ts +0 -221
  101. package/dist/utils/utils.d.ts.map +0 -1
  102. package/dist/utils/utils.js.map +0 -1
  103. /package/frontend/build/static/js/{main.6df4ebe4.js.LICENSE.txt → main.26dbf9b9.js.LICENSE.txt} +0 -0
package/dist/frontend.js CHANGED
@@ -1,28 +1,4 @@
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.0
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
1
  import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat } from '@matter/main';
25
- // Node modules
26
2
  import { createServer } from 'http';
27
3
  import https from 'https';
28
4
  import express from 'express';
@@ -30,32 +6,13 @@ import WebSocket, { WebSocketServer } from 'ws';
30
6
  import os from 'os';
31
7
  import path from 'path';
32
8
  import { promises as fs } from 'fs';
33
- // AnsiLogger module
34
9
  import { AnsiLogger, CYAN, db, debugStringify, er, nf, rs, stringify, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
35
- // Matterbridge
36
10
  import { createZip, hasParameter, isValidNumber, isValidObject, isValidString } from './utils/utils.js';
37
11
  import { plg } from './matterbridgeTypes.js';
38
12
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
39
- /**
40
- * Websocket message ID for logging.
41
- * @constant {number}
42
- */
43
13
  export const WS_ID_LOG = 0;
44
- /**
45
- * Websocket message ID indicating a refresh is needed.
46
- * @constant {number}
47
- */
48
14
  export const WS_ID_REFRESH_NEEDED = 1;
49
- /**
50
- * Websocket message ID indicating a restart is needed.
51
- * @constant {number}
52
- */
53
15
  export const WS_ID_RESTART_NEEDED = 2;
54
- /**
55
- * Initializes the frontend of Matterbridge.
56
- *
57
- * @param port The port number to run the frontend server on. Default is 8283.
58
- */
59
16
  export class Frontend {
60
17
  matterbridge;
61
18
  log;
@@ -67,26 +24,15 @@ export class Frontend {
67
24
  webSocketServer;
68
25
  constructor(matterbridge) {
69
26
  this.matterbridge = matterbridge;
70
- this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
27
+ this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
71
28
  }
72
29
  async start(port = 8283) {
73
30
  this.port = port;
74
31
  this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
75
- // Create the express app that serves the frontend
76
32
  this.expressApp = express();
77
- // Log all requests to the server for debugging
78
- /*
79
- this.expressApp.use((req, res, next) => {
80
- this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
81
- next();
82
- });
83
- */
84
- // Serve static files from '/static' endpoint
85
33
  this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
86
34
  if (!hasParameter('ssl')) {
87
- // Create an HTTP server and attach the express app
88
35
  this.httpServer = createServer(this.expressApp);
89
- // Listen on the specified port
90
36
  if (hasParameter('ingress')) {
91
37
  this.httpServer.listen(this.port, '0.0.0.0', () => {
92
38
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -100,7 +46,6 @@ export class Frontend {
100
46
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
101
47
  });
102
48
  }
103
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
49
  this.httpServer.on('error', (error) => {
105
50
  this.log.error(`Frontend http server error listening on ${this.port}`);
106
51
  switch (error.code) {
@@ -116,7 +61,6 @@ export class Frontend {
116
61
  });
117
62
  }
118
63
  else {
119
- // Load the SSL certificate, the private key and optionally the CA certificate
120
64
  let cert;
121
65
  try {
122
66
  cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
@@ -144,9 +88,7 @@ export class Frontend {
144
88
  this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
145
89
  }
146
90
  const serverOptions = { cert, key, ca };
147
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
148
91
  this.httpsServer = https.createServer(serverOptions, this.expressApp);
149
- // Listen on the specified port
150
92
  if (hasParameter('ingress')) {
151
93
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
152
94
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
@@ -160,7 +102,6 @@ export class Frontend {
160
102
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
161
103
  });
162
104
  }
163
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
105
  this.httpsServer.on('error', (error) => {
165
106
  this.log.error(`Frontend https server error listening on ${this.port}`);
166
107
  switch (error.code) {
@@ -177,13 +118,12 @@ export class Frontend {
177
118
  }
178
119
  if (this.initializeError)
179
120
  return;
180
- // Createe a WebSocket server and attach it to the http or https server
181
121
  const wssPort = this.port;
182
122
  const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
183
123
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
184
124
  this.webSocketServer.on('connection', (ws, request) => {
185
125
  const clientIp = request.socket.remoteAddress;
186
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
126
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
187
127
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
188
128
  ws.on('message', (message) => {
189
129
  this.wsMessageHandler(ws, message);
@@ -215,7 +155,6 @@ export class Frontend {
215
155
  this.webSocketServer.on('error', (ws, error) => {
216
156
  this.log.error(`WebSocketServer error: ${error}`);
217
157
  });
218
- // Endpoint to validate login code
219
158
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
220
159
  const { password } = req.body;
221
160
  this.log.debug('The frontend sent /api/login', password);
@@ -234,33 +173,28 @@ export class Frontend {
234
173
  this.log.warn('/api/login error wrong password');
235
174
  res.json({ valid: false });
236
175
  }
237
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
238
176
  }
239
177
  catch (error) {
240
178
  this.log.error('/api/login error getting password');
241
179
  res.json({ valid: false });
242
180
  }
243
181
  });
244
- // Endpoint to provide health check
245
182
  this.expressApp.get('/health', (req, res) => {
246
183
  this.log.debug('Express received /health');
247
184
  const healthStatus = {
248
- status: 'ok', // Indicate service is healthy
249
- uptime: process.uptime(), // Server uptime in seconds
250
- timestamp: new Date().toISOString(), // Current timestamp
185
+ status: 'ok',
186
+ uptime: process.uptime(),
187
+ timestamp: new Date().toISOString(),
251
188
  };
252
189
  res.status(200).json(healthStatus);
253
190
  });
254
- // Endpoint to provide memory usage details
255
191
  this.expressApp.get('/memory', async (req, res) => {
256
192
  this.log.debug('Express received /memory');
257
- // Function to format bytes to KB or MB
258
193
  const formatMemoryUsage = (bytes) => {
259
194
  const kb = bytes / 1024;
260
195
  const mb = kb / 1024;
261
196
  return mb >= 1 ? `${mb.toFixed(2)} MB` : `${kb.toFixed(2)} KB`;
262
197
  };
263
- // Memory usage from process
264
198
  const memoryUsageRaw = process.memoryUsage();
265
199
  const memoryUsage = {
266
200
  rss: formatMemoryUsage(memoryUsageRaw.rss),
@@ -269,13 +203,10 @@ export class Frontend {
269
203
  external: formatMemoryUsage(memoryUsageRaw.external),
270
204
  arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
271
205
  };
272
- // V8 heap statistics
273
206
  const { default: v8 } = await import('node:v8');
274
207
  const heapStatsRaw = v8.getHeapStatistics();
275
208
  const heapSpacesRaw = v8.getHeapSpaceStatistics();
276
- // Format heapStats
277
209
  const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
278
- // Format heapSpaces
279
210
  const heapSpaces = heapSpacesRaw.map((space) => ({
280
211
  ...space,
281
212
  space_size: formatMemoryUsage(space.space_size),
@@ -293,42 +224,29 @@ export class Frontend {
293
224
  };
294
225
  res.status(200).json(memoryReport);
295
226
  });
296
- // Endpoint to provide settings
227
+ this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
228
+ const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
229
+ if (pairingCodes) {
230
+ const { manualPairingCode, qrPairingCode } = pairingCodes;
231
+ res.json({ manualPairingCode, qrPairingCode: 'https://project-chip.github.io/connectedhomeip/qrcode.html?data=' + qrPairingCode });
232
+ }
233
+ else {
234
+ res.status(500).json({ error: 'Failed to generate pairing codes' });
235
+ }
236
+ });
297
237
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
298
238
  this.log.debug('The frontend sent /api/settings');
299
- this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
300
- this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
301
- this.matterbridge.matterbridgeInformation.loggerLevel = this.log.logLevel;
302
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
303
- this.matterbridge.matterbridgeInformation.mattermdnsinterface = (await this.matterbridge.nodeContext?.get('mattermdnsinterface', '')) || '';
304
- this.matterbridge.matterbridgeInformation.matteripv4address = (await this.matterbridge.nodeContext?.get('matteripv4address', '')) || '';
305
- this.matterbridge.matterbridgeInformation.matteripv6address = (await this.matterbridge.nodeContext?.get('matteripv6address', '')) || '';
306
- this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
307
- this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
308
- this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
309
- this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.matterbridgePaired;
310
- this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeQrPairingCode;
311
- this.matterbridge.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridge.matterbridgeManualPairingCode;
312
- this.matterbridge.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridge.matterbridgeFabricInformations;
313
- this.matterbridge.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridge.matterbridgeSessionInformations.values());
314
- this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
315
- const response = { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
316
- // this.log.debug('Response:', debugStringify(response));
317
- res.json(response);
239
+ res.json(await this.getApiSettings());
318
240
  });
319
- // Endpoint to provide plugins
320
241
  this.expressApp.get('/api/plugins', async (req, res) => {
321
242
  this.log.debug('The frontend sent /api/plugins');
322
- const response = await this.getBaseRegisteredPlugins();
323
- // this.log.debug('Response:', debugStringify(response));
243
+ const response = this.getBaseRegisteredPlugins();
324
244
  res.json(response);
325
245
  });
326
- // Endpoint to provide devices
327
246
  this.expressApp.get('/api/devices', (req, res) => {
328
247
  this.log.debug('The frontend sent /api/devices');
329
248
  const devices = [];
330
249
  this.matterbridge.devices.forEach(async (device) => {
331
- // Check if the device has the required properties
332
250
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
333
251
  return;
334
252
  const cluster = this.getClusterTextFromDevice(device);
@@ -344,10 +262,8 @@ export class Frontend {
344
262
  cluster: cluster,
345
263
  });
346
264
  });
347
- // this.log.debug('Response:', debugStringify(data));
348
265
  res.json(devices);
349
266
  });
350
- // Endpoint to provide the cluster servers of the devices
351
267
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
352
268
  const selectedPluginName = req.params.selectedPluginName;
353
269
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
@@ -420,7 +336,6 @@ export class Frontend {
420
336
  });
421
337
  res.json(data);
422
338
  });
423
- // Endpoint to view the log
424
339
  this.expressApp.get('/api/view-log', async (req, res) => {
425
340
  this.log.debug('The frontend sent /api/log');
426
341
  try {
@@ -433,12 +348,10 @@ export class Frontend {
433
348
  res.status(500).send('Error reading log file');
434
349
  }
435
350
  });
436
- // Endpoint to download the matterbridge log
437
351
  this.expressApp.get('/api/download-mblog', async (req, res) => {
438
352
  this.log.debug('The frontend sent /api/download-mblog');
439
353
  try {
440
354
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
441
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
442
355
  }
443
356
  catch (error) {
444
357
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -450,12 +363,10 @@ export class Frontend {
450
363
  }
451
364
  });
452
365
  });
453
- // Endpoint to download the matter log
454
366
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
455
367
  this.log.debug('The frontend sent /api/download-mjlog');
456
368
  try {
457
369
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
458
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
459
370
  }
460
371
  catch (error) {
461
372
  fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
@@ -467,7 +378,6 @@ export class Frontend {
467
378
  }
468
379
  });
469
380
  });
470
- // Endpoint to download the matter storage file
471
381
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
472
382
  this.log.debug('The frontend sent /api/download-mjstorage');
473
383
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
@@ -478,7 +388,6 @@ export class Frontend {
478
388
  }
479
389
  });
480
390
  });
481
- // Endpoint to download the matterbridge storage directory
482
391
  this.expressApp.get('/api/download-mbstorage', async (req, res) => {
483
392
  this.log.debug('The frontend sent /api/download-mbstorage');
484
393
  await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
@@ -489,7 +398,6 @@ export class Frontend {
489
398
  }
490
399
  });
491
400
  });
492
- // Endpoint to download the matterbridge plugin directory
493
401
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
494
402
  this.log.debug('The frontend sent /api/download-pluginstorage');
495
403
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
@@ -500,11 +408,9 @@ export class Frontend {
500
408
  }
501
409
  });
502
410
  });
503
- // Endpoint to download the matterbridge plugin config files
504
411
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
505
412
  this.log.debug('The frontend sent /api/download-pluginconfig');
506
413
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
507
- // 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')));
508
414
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
509
415
  if (error) {
510
416
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
@@ -512,7 +418,6 @@ export class Frontend {
512
418
  }
513
419
  });
514
420
  });
515
- // Endpoint to download the matterbridge plugin config files
516
421
  this.expressApp.get('/api/download-backup', async (req, res) => {
517
422
  this.log.debug('The frontend sent /api/download-backup');
518
423
  res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
@@ -522,7 +427,6 @@ export class Frontend {
522
427
  }
523
428
  });
524
429
  });
525
- // Endpoint to receive commands
526
430
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
527
431
  const command = req.params.command;
528
432
  let param = req.params.param;
@@ -532,15 +436,13 @@ export class Frontend {
532
436
  return;
533
437
  }
534
438
  this.log.debug(`Received frontend command: ${command}:${param}`);
535
- // Handle the command setpassword from Settings
536
439
  if (command === 'setpassword') {
537
- const password = param.slice(1, -1); // Remove the first and last characters
440
+ const password = param.slice(1, -1);
538
441
  this.log.debug('setpassword', param, password);
539
442
  await this.matterbridge.nodeContext?.set('password', password);
540
443
  res.json({ message: 'Command received' });
541
444
  return;
542
445
  }
543
- // Handle the command setbridgemode from Settings
544
446
  if (command === 'setbridgemode') {
545
447
  this.log.debug(`setbridgemode: ${param}`);
546
448
  this.wssSendRestartRequired();
@@ -548,7 +450,6 @@ export class Frontend {
548
450
  res.json({ message: 'Command received' });
549
451
  return;
550
452
  }
551
- // Handle the command backup from Settings
552
453
  if (command === 'backup') {
553
454
  this.log.notice(`Prepairing the backup...`);
554
455
  await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
@@ -556,26 +457,25 @@ export class Frontend {
556
457
  res.json({ message: 'Command received' });
557
458
  return;
558
459
  }
559
- // Handle the command setmbloglevel from Settings
560
460
  if (command === 'setmbloglevel') {
561
461
  this.log.debug('Matterbridge log level:', param);
562
462
  if (param === 'Debug') {
563
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
463
+ this.log.logLevel = "debug";
564
464
  }
565
465
  else if (param === 'Info') {
566
- this.log.logLevel = "info" /* LogLevel.INFO */;
466
+ this.log.logLevel = "info";
567
467
  }
568
468
  else if (param === 'Notice') {
569
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
469
+ this.log.logLevel = "notice";
570
470
  }
571
471
  else if (param === 'Warn') {
572
- this.log.logLevel = "warn" /* LogLevel.WARN */;
472
+ this.log.logLevel = "warn";
573
473
  }
574
474
  else if (param === 'Error') {
575
- this.log.logLevel = "error" /* LogLevel.ERROR */;
475
+ this.log.logLevel = "error";
576
476
  }
577
477
  else if (param === 'Fatal') {
578
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
478
+ this.log.logLevel = "fatal";
579
479
  }
580
480
  await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
581
481
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
@@ -583,13 +483,12 @@ export class Frontend {
583
483
  for (const plugin of this.matterbridge.plugins) {
584
484
  if (!plugin.platform || !plugin.platform.config)
585
485
  continue;
586
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
587
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
486
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
487
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
588
488
  }
589
489
  res.json({ message: 'Command received' });
590
490
  return;
591
491
  }
592
- // Handle the command setmbloglevel from Settings
593
492
  if (command === 'setmjloglevel') {
594
493
  this.log.debug('Matter.js log level:', param);
595
494
  if (param === 'Debug') {
@@ -614,34 +513,30 @@ export class Frontend {
614
513
  res.json({ message: 'Command received' });
615
514
  return;
616
515
  }
617
- // Handle the command setmdnsinterface from Settings
618
516
  if (command === 'setmdnsinterface') {
619
- param = param.slice(1, -1); // Remove the first and last characters *mdns*
517
+ param = param.slice(1, -1);
620
518
  this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
621
519
  this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
622
520
  await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
623
521
  res.json({ message: 'Command received' });
624
522
  return;
625
523
  }
626
- // Handle the command setipv4address from Settings
627
524
  if (command === 'setipv4address') {
628
- param = param.slice(1, -1); // Remove the first and last characters *ip*
525
+ param = param.slice(1, -1);
629
526
  this.matterbridge.matterbridgeInformation.matteripv4address = param;
630
527
  this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
631
528
  await this.matterbridge.nodeContext?.set('matteripv4address', param);
632
529
  res.json({ message: 'Command received' });
633
530
  return;
634
531
  }
635
- // Handle the command setipv6address from Settings
636
532
  if (command === 'setipv6address') {
637
- param = param.slice(1, -1); // Remove the first and last characters *ip*
533
+ param = param.slice(1, -1);
638
534
  this.matterbridge.matterbridgeInformation.matteripv6address = param;
639
535
  this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
640
536
  await this.matterbridge.nodeContext?.set('matteripv6address', param);
641
537
  res.json({ message: 'Command received' });
642
538
  return;
643
539
  }
644
- // Handle the command setmatterport from Settings
645
540
  if (command === 'setmatterport') {
646
541
  const port = Math.min(Math.max(parseInt(param), 5540), 5560);
647
542
  this.matterbridge.matterbridgeInformation.matterPort = port;
@@ -650,7 +545,6 @@ export class Frontend {
650
545
  res.json({ message: 'Command received' });
651
546
  return;
652
547
  }
653
- // Handle the command setmatterdiscriminator from Settings
654
548
  if (command === 'setmatterdiscriminator') {
655
549
  const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
656
550
  this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
@@ -659,7 +553,6 @@ export class Frontend {
659
553
  res.json({ message: 'Command received' });
660
554
  return;
661
555
  }
662
- // Handle the command setmatterpasscode from Settings
663
556
  if (command === 'setmatterpasscode') {
664
557
  const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
665
558
  this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
@@ -668,20 +561,17 @@ export class Frontend {
668
561
  res.json({ message: 'Command received' });
669
562
  return;
670
563
  }
671
- // Handle the command setmbloglevel from Settings
672
564
  if (command === 'setmblogfile') {
673
565
  this.log.debug('Matterbridge file log:', param);
674
566
  this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
675
567
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
676
- // Create the file logger for matterbridge
677
568
  if (param === 'true')
678
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
569
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
679
570
  else
680
571
  AnsiLogger.setGlobalLogfile(undefined);
681
572
  res.json({ message: 'Command received' });
682
573
  return;
683
574
  }
684
- // Handle the command setmbloglevel from Settings
685
575
  if (command === 'setmjlogfile') {
686
576
  this.log.debug('Matter file log:', param);
687
577
  this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
@@ -708,43 +598,36 @@ export class Frontend {
708
598
  res.json({ message: 'Command received' });
709
599
  return;
710
600
  }
711
- // Handle the command unregister from Settings
712
601
  if (command === 'unregister') {
713
602
  await this.matterbridge.unregisterAndShutdownProcess();
714
603
  res.json({ message: 'Command received' });
715
604
  return;
716
605
  }
717
- // Handle the command reset from Settings
718
606
  if (command === 'reset') {
719
607
  await this.matterbridge.shutdownProcessAndReset();
720
608
  res.json({ message: 'Command received' });
721
609
  return;
722
610
  }
723
- // Handle the command factoryreset from Settings
724
611
  if (command === 'factoryreset') {
725
612
  await this.matterbridge.shutdownProcessAndFactoryReset();
726
613
  res.json({ message: 'Command received' });
727
614
  return;
728
615
  }
729
- // Handle the command shutdown from Header
730
616
  if (command === 'shutdown') {
731
617
  await this.matterbridge.shutdownProcess();
732
618
  res.json({ message: 'Command received' });
733
619
  return;
734
620
  }
735
- // Handle the command restart from Header
736
621
  if (command === 'restart') {
737
622
  await this.matterbridge.restartProcess();
738
623
  res.json({ message: 'Command received' });
739
624
  return;
740
625
  }
741
- // Handle the command update from Header
742
626
  if (command === 'update') {
743
627
  this.log.info('Updating matterbridge...');
744
628
  try {
745
629
  await this.matterbridge.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
746
630
  this.log.info('Matterbridge has been updated. Full restart required.');
747
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
748
631
  }
749
632
  catch (error) {
750
633
  this.log.error('Error updating matterbridge');
@@ -754,11 +637,9 @@ export class Frontend {
754
637
  res.json({ message: 'Command received' });
755
638
  return;
756
639
  }
757
- // Handle the command saveconfig from Home
758
640
  if (command === 'saveconfig') {
759
641
  param = param.replace(/\*/g, '\\');
760
642
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
761
- // console.log('Req.body:', JSON.stringify(req.body, null, 2));
762
643
  if (!this.matterbridge.plugins.has(param)) {
763
644
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
764
645
  }
@@ -772,58 +653,49 @@ export class Frontend {
772
653
  res.json({ message: 'Command received' });
773
654
  return;
774
655
  }
775
- // Handle the command installplugin from Home
776
656
  if (command === 'installplugin') {
777
657
  param = param.replace(/\*/g, '\\');
778
658
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
779
659
  try {
780
660
  await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
781
661
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
782
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
783
662
  }
784
663
  catch (error) {
785
664
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
786
665
  }
787
666
  this.wssSendRestartRequired();
788
667
  param = param.split('@')[0];
789
- // Also add the plugin to matterbridge so no return!
790
668
  if (param === 'matterbridge') {
791
- // 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
792
669
  res.json({ message: 'Command received' });
793
670
  return;
794
671
  }
795
672
  }
796
- // Handle the command addplugin from Home
797
673
  if (command === 'addplugin' || command === 'installplugin') {
798
674
  param = param.replace(/\*/g, '\\');
799
675
  const plugin = await this.matterbridge.plugins.add(param);
800
676
  if (plugin) {
801
677
  if (this.matterbridge.bridgeMode === 'childbridge') {
802
- // 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
803
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
804
678
  this.matterbridge.createDynamicPlugin(plugin, true);
805
679
  }
806
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
680
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true);
807
681
  }
808
682
  res.json({ message: 'Command received' });
809
683
  this.wssSendRefreshRequired();
810
684
  return;
811
685
  }
812
- // Handle the command removeplugin from Home
813
686
  if (command === 'removeplugin') {
814
687
  if (!this.matterbridge.plugins.has(param)) {
815
688
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
816
689
  }
817
690
  else {
818
691
  const plugin = this.matterbridge.plugins.get(param);
819
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
692
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
820
693
  await this.matterbridge.plugins.remove(param);
821
694
  }
822
695
  res.json({ message: 'Command received' });
823
696
  this.wssSendRefreshRequired();
824
697
  return;
825
698
  }
826
- // Handle the command enableplugin from Home
827
699
  if (command === 'enableplugin') {
828
700
  if (!this.matterbridge.plugins.has(param)) {
829
701
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -841,17 +713,15 @@ export class Frontend {
841
713
  plugin.addedDevices = undefined;
842
714
  await this.matterbridge.plugins.enable(param);
843
715
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
844
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
845
716
  this.matterbridge.createDynamicPlugin(plugin, true);
846
717
  }
847
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background since the server node is already started
718
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
848
719
  }
849
720
  }
850
721
  res.json({ message: 'Command received' });
851
722
  this.wssSendRefreshRequired();
852
723
  return;
853
724
  }
854
- // Handle the command disableplugin from Home
855
725
  if (command === 'disableplugin') {
856
726
  if (!this.matterbridge.plugins.has(param)) {
857
727
  this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
@@ -859,7 +729,7 @@ export class Frontend {
859
729
  else {
860
730
  const plugin = this.matterbridge.plugins.get(param);
861
731
  if (plugin && plugin.enabled) {
862
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
732
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
863
733
  await this.matterbridge.plugins.disable(param);
864
734
  }
865
735
  }
@@ -868,7 +738,6 @@ export class Frontend {
868
738
  return;
869
739
  }
870
740
  });
871
- // Fallback for routing (must be the last route)
872
741
  this.expressApp.get('*', (req, res) => {
873
742
  this.log.debug('The frontend sent:', req.url);
874
743
  this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
@@ -877,29 +746,24 @@ export class Frontend {
877
746
  this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
878
747
  }
879
748
  async stop() {
880
- // Close the http server
881
749
  if (this.httpServer) {
882
750
  this.httpServer.close();
883
751
  this.httpServer.removeAllListeners();
884
752
  this.httpServer = undefined;
885
753
  this.log.debug('Frontend http server closed successfully');
886
754
  }
887
- // Close the https server
888
755
  if (this.httpsServer) {
889
756
  this.httpsServer.close();
890
757
  this.httpsServer.removeAllListeners();
891
758
  this.httpsServer = undefined;
892
759
  this.log.debug('Frontend https server closed successfully');
893
760
  }
894
- // Remove listeners from the express app
895
761
  if (this.expressApp) {
896
762
  this.expressApp.removeAllListeners();
897
763
  this.expressApp = undefined;
898
764
  this.log.debug('Frontend app closed successfully');
899
765
  }
900
- // Close the WebSocket server
901
766
  if (this.webSocketServer) {
902
- // Close all active connections
903
767
  this.webSocketServer.clients.forEach((client) => {
904
768
  if (client.readyState === WebSocket.OPEN) {
905
769
  client.close();
@@ -916,11 +780,25 @@ export class Frontend {
916
780
  this.webSocketServer = undefined;
917
781
  }
918
782
  }
919
- /**
920
- * Retrieves the cluster text description from a given device.
921
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
922
- * @returns {string} The attributes description of the cluster servers in the device.
923
- */
783
+ async getApiSettings() {
784
+ this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
785
+ this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
786
+ this.matterbridge.matterbridgeInformation.loggerLevel = this.log.logLevel;
787
+ this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
788
+ this.matterbridge.matterbridgeInformation.mattermdnsinterface = this.matterbridge.mdnsInterface;
789
+ this.matterbridge.matterbridgeInformation.matteripv4address = this.matterbridge.ipv4address;
790
+ this.matterbridge.matterbridgeInformation.matteripv6address = this.matterbridge.ipv6address;
791
+ this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
792
+ this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
793
+ this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
794
+ this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.matterbridgePaired;
795
+ this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeQrPairingCode;
796
+ this.matterbridge.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridge.matterbridgeManualPairingCode;
797
+ this.matterbridge.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridge.matterbridgeFabricInformations;
798
+ this.matterbridge.matterbridgeInformation.matterbridgeSessionInformations = this.matterbridge.matterbridgeSessionInformations;
799
+ this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
800
+ return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
801
+ }
924
802
  getClusterTextFromDevice(device) {
925
803
  const getAttribute = (device, cluster, attribute) => {
926
804
  let value = undefined;
@@ -958,85 +836,87 @@ export class Frontend {
958
836
  return '';
959
837
  };
960
838
  let attributes = '';
961
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
962
- Object.entries(device.state).forEach(([clusterName, clusterAttributes]) => {
963
- Object.entries(clusterAttributes).forEach(([attributeName, attributeValue]) => {
964
- // console.log(`Cluster: ${clusterName} Attribute: ${attributeName} Value: ${attributeValue}`);
965
- if (clusterName === 'onOff' && attributeName === 'onOff')
966
- attributes += `OnOff: ${attributeValue} `;
967
- if (clusterName === 'switch' && attributeName === 'currentPosition')
968
- attributes += `Position: ${attributeValue} `;
969
- if (clusterName === 'windowCovering' && attributeName === 'currentPositionLiftPercent100ths')
970
- attributes += `Cover position: ${attributeValue / 100}% `;
971
- if (clusterName === 'doorLock' && attributeName === 'lockState')
972
- attributes += `State: ${attributeValue === 1 ? 'Locked' : 'Not locked'} `;
973
- if (clusterName === 'thermostat' && attributeName === 'localTemperature')
974
- attributes += `Temperature: ${attributeValue / 100}°C `;
975
- if (clusterName === 'thermostat' && attributeName === 'occupiedHeatingSetpoint')
976
- attributes += `Heat to: ${attributeValue / 100}°C `;
977
- if (clusterName === 'thermostat' && attributeName === 'occupiedCoolingSetpoint')
978
- attributes += `Cool to: ${attributeValue / 100}°C `;
979
- if (clusterName === 'pumpConfigurationAndControl' && attributeName === 'operationMode')
980
- attributes += `Mode: ${attributeValue} `;
981
- if (clusterName === 'valveConfigurationAndControl' && attributeName === 'currentState')
982
- attributes += `State: ${attributeValue} `;
983
- if (clusterName === 'levelControl' && attributeName === 'currentLevel')
984
- attributes += `Level: ${attributeValue}% `;
985
- if (clusterName === 'colorControl' && attributeName === 'colorMode')
986
- attributes += `Mode: ${['HS', 'XY', 'CT'][attributeValue]} `;
987
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentHue')
988
- attributes += `Hue: ${Math.round(attributeValue)} `;
989
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentSaturation')
990
- attributes += `Saturation: ${Math.round(attributeValue)} `;
991
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentX')
992
- attributes += `X: ${Math.round(attributeValue)} `;
993
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentY')
994
- attributes += `Y: ${Math.round(attributeValue)} `;
995
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 2 && attributeName === 'colorTemperatureMireds')
996
- attributes += `ColorTemp: ${Math.round(attributeValue)} `;
997
- if (clusterName === 'booleanState' && attributeName === 'stateValue')
998
- attributes += `Contact: ${attributeValue} `;
999
- if (clusterName === 'booleanStateConfiguration' && attributeName === 'alarmsActive')
1000
- attributes += `Active alarms: ${stringify(attributeValue)} `;
1001
- if (clusterName === 'smokeCoAlarm' && attributeName === 'smokeState')
1002
- attributes += `Smoke: ${attributeValue} `;
1003
- if (clusterName === 'smokeCoAlarm' && attributeName === 'coState')
1004
- attributes += `Co: ${attributeValue} `;
1005
- if (clusterName === 'fanControl' && attributeName === 'fanMode')
1006
- attributes += `Mode: ${attributeValue} `;
1007
- if (clusterName === 'fanControl' && attributeName === 'percentCurrent')
1008
- attributes += `Percent: ${attributeValue} `;
1009
- if (clusterName === 'fanControl' && attributeName === 'speedCurrent')
1010
- attributes += `Speed: ${attributeValue} `;
1011
- if (clusterName === 'occupancySensing' && attributeName === 'occupancy')
1012
- attributes += `Occupancy: ${attributeValue.occupied} `;
1013
- if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue')
1014
- attributes += `Illuminance: ${attributeValue} `;
1015
- if (clusterName === 'airQuality' && attributeName === 'airQuality')
1016
- attributes += `Air quality: ${attributeValue} `;
1017
- if (clusterName === 'tvocMeasurement' && attributeName === 'measuredValue')
1018
- attributes += `Voc: ${attributeValue} `;
1019
- if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue')
1020
- attributes += `Temperature: ${attributeValue / 100}°C `;
1021
- if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue')
1022
- attributes += `Humidity: ${attributeValue / 100}% `;
1023
- if (clusterName === 'pressureMeasurement' && attributeName === 'measuredValue')
1024
- attributes += `Pressure: ${attributeValue} `;
1025
- if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue')
1026
- attributes += `Flow: ${attributeValue} `;
1027
- if (clusterName === 'fixedLabel' && attributeName === 'labelList')
1028
- attributes += `${getFixedLabel(device)} `;
1029
- if (clusterName === 'userLabel' && attributeName === 'labelList')
1030
- attributes += `${getUserLabel(device)} `;
1031
- });
839
+ device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
840
+ if (typeof attributeValue === 'undefined')
841
+ return;
842
+ if (clusterName === 'onOff' && attributeName === 'onOff')
843
+ attributes += `OnOff: ${attributeValue} `;
844
+ if (clusterName === 'switch' && attributeName === 'currentPosition')
845
+ attributes += `Position: ${attributeValue} `;
846
+ if (clusterName === 'windowCovering' && attributeName === 'currentPositionLiftPercent100ths' && isValidNumber(attributeValue, 0, 10000))
847
+ attributes += `Cover position: ${attributeValue / 100}% `;
848
+ if (clusterName === 'doorLock' && attributeName === 'lockState')
849
+ attributes += `State: ${attributeValue === 1 ? 'Locked' : 'Not locked'} `;
850
+ if (clusterName === 'thermostat' && attributeName === 'localTemperature' && isValidNumber(attributeValue))
851
+ attributes += `Temperature: ${attributeValue / 100}°C `;
852
+ if (clusterName === 'thermostat' && attributeName === 'occupiedHeatingSetpoint' && isValidNumber(attributeValue))
853
+ attributes += `Heat to: ${attributeValue / 100}°C `;
854
+ if (clusterName === 'thermostat' && attributeName === 'occupiedCoolingSetpoint' && isValidNumber(attributeValue))
855
+ attributes += `Cool to: ${attributeValue / 100}°C `;
856
+ if (clusterName === 'pumpConfigurationAndControl' && attributeName === 'operationMode')
857
+ attributes += `Mode: ${attributeValue} `;
858
+ if (clusterName === 'valveConfigurationAndControl' && attributeName === 'currentState')
859
+ attributes += `State: ${attributeValue} `;
860
+ if (clusterName === 'levelControl' && attributeName === 'currentLevel')
861
+ attributes += `Level: ${attributeValue} `;
862
+ if (clusterName === 'colorControl' && attributeName === 'colorMode' && isValidNumber(attributeValue, 0, 2))
863
+ attributes += `Mode: ${['HS', 'XY', 'CT'][attributeValue]} `;
864
+ if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentHue' && isValidNumber(attributeValue))
865
+ attributes += `Hue: ${Math.round(attributeValue)} `;
866
+ if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentSaturation' && isValidNumber(attributeValue))
867
+ attributes += `Saturation: ${Math.round(attributeValue)} `;
868
+ if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentX' && isValidNumber(attributeValue))
869
+ attributes += `X: ${Math.round(attributeValue / 655.36) / 100} `;
870
+ if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentY' && isValidNumber(attributeValue))
871
+ attributes += `Y: ${Math.round(attributeValue / 655.36) / 100} `;
872
+ if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 2 && attributeName === 'colorTemperatureMireds' && isValidNumber(attributeValue))
873
+ attributes += `ColorTemp: ${Math.round(attributeValue)} `;
874
+ if (clusterName === 'booleanState' && attributeName === 'stateValue')
875
+ attributes += `Contact: ${attributeValue} `;
876
+ if (clusterName === 'booleanStateConfiguration' && attributeName === 'alarmsActive' && isValidObject(attributeValue))
877
+ attributes += `Active alarms: ${stringify(attributeValue)} `;
878
+ if (clusterName === 'smokeCoAlarm' && attributeName === 'smokeState')
879
+ attributes += `Smoke: ${attributeValue} `;
880
+ if (clusterName === 'smokeCoAlarm' && attributeName === 'coState')
881
+ attributes += `Co: ${attributeValue} `;
882
+ if (clusterName === 'fanControl' && attributeName === 'fanMode')
883
+ attributes += `Mode: ${attributeValue} `;
884
+ if (clusterName === 'fanControl' && attributeName === 'percentCurrent')
885
+ attributes += `Percent: ${attributeValue} `;
886
+ if (clusterName === 'fanControl' && attributeName === 'speedCurrent')
887
+ attributes += `Speed: ${attributeValue} `;
888
+ if (clusterName === 'occupancySensing' && attributeName === 'occupancy' && isValidObject(attributeValue, 1))
889
+ attributes += `Occupancy: ${attributeValue.occupied} `;
890
+ if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue' && isValidNumber(attributeValue))
891
+ attributes += `Illuminance: ${Math.round(Math.max(Math.pow(10, attributeValue / 10000), 0))} `;
892
+ if (clusterName === 'airQuality' && attributeName === 'airQuality')
893
+ attributes += `Air quality: ${attributeValue} `;
894
+ if (clusterName === 'totalVolatileOrganicCompoundsConcentrationMeasurement' && attributeName === 'measuredValue')
895
+ attributes += `Voc: ${attributeValue} `;
896
+ if (clusterName === 'pm1ConcentrationMeasurement' && attributeName === 'measuredValue')
897
+ attributes += `Pm1: ${attributeValue} `;
898
+ if (clusterName === 'pm25ConcentrationMeasurement' && attributeName === 'measuredValue')
899
+ attributes += `Pm2.5: ${attributeValue} `;
900
+ if (clusterName === 'pm10ConcentrationMeasurement' && attributeName === 'measuredValue')
901
+ attributes += `Pm10: ${attributeValue} `;
902
+ if (clusterName === 'formaldehydeConcentrationMeasurement' && attributeName === 'measuredValue')
903
+ attributes += `CH₂O: ${attributeValue} `;
904
+ if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue' && isValidNumber(attributeValue))
905
+ attributes += `Temperature: ${attributeValue / 100}°C `;
906
+ if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue' && isValidNumber(attributeValue))
907
+ attributes += `Humidity: ${attributeValue / 100}% `;
908
+ if (clusterName === 'pressureMeasurement' && attributeName === 'measuredValue')
909
+ attributes += `Pressure: ${attributeValue} `;
910
+ if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue')
911
+ attributes += `Flow: ${attributeValue} `;
912
+ if (clusterName === 'fixedLabel' && attributeName === 'labelList')
913
+ attributes += `${getFixedLabel(device)} `;
914
+ if (clusterName === 'userLabel' && attributeName === 'labelList')
915
+ attributes += `${getUserLabel(device)} `;
1032
916
  });
1033
917
  return attributes.trimStart().trimEnd();
1034
918
  }
1035
- /**
1036
- * Retrieves the base registered plugins sanitized for res.json().
1037
- * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
1038
- */
1039
- async getBaseRegisteredPlugins() {
919
+ getBaseRegisteredPlugins() {
1040
920
  const baseRegisteredPlugins = [];
1041
921
  for (const plugin of this.matterbridge.plugins) {
1042
922
  baseRegisteredPlugins.push({
@@ -1066,14 +946,6 @@ export class Frontend {
1066
946
  }
1067
947
  return baseRegisteredPlugins;
1068
948
  }
1069
- /**
1070
- * Handles incoming websocket messages for the Matterbridge.
1071
- *
1072
- * @param {Matterbridge} this - The Matterbridge instance.
1073
- * @param {WebSocket} client - The websocket client that sent the message.
1074
- * @param {WebSocket.RawData} message - The raw data of the message received from the client.
1075
- * @returns {Promise<void>} A promise that resolves when the message has been handled.
1076
- */
1077
949
  async wsMessageHandler(client, message) {
1078
950
  let data;
1079
951
  try {
@@ -1144,39 +1016,25 @@ export class Frontend {
1144
1016
  await this.matterbridge.shutdownProcess();
1145
1017
  return;
1146
1018
  }
1019
+ else if (data.method === '/api/advertise') {
1020
+ const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
1021
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes }));
1022
+ return;
1023
+ }
1147
1024
  else if (data.method === '/api/settings') {
1148
- this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
1149
- this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
1150
- this.matterbridge.matterbridgeInformation.loggerLevel = this.log.logLevel;
1151
- this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
1152
- this.matterbridge.matterbridgeInformation.mattermdnsinterface = (await this.matterbridge.nodeContext?.get('mattermdnsinterface', '')) || '';
1153
- this.matterbridge.matterbridgeInformation.matteripv4address = (await this.matterbridge.nodeContext?.get('matteripv4address', '')) || '';
1154
- this.matterbridge.matterbridgeInformation.matteripv6address = (await this.matterbridge.nodeContext?.get('matteripv6address', '')) || '';
1155
- this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
1156
- this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
1157
- this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
1158
- this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.matterbridgePaired;
1159
- this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeQrPairingCode;
1160
- this.matterbridge.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridge.matterbridgeManualPairingCode;
1161
- this.matterbridge.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridge.matterbridgeFabricInformations;
1162
- this.matterbridge.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridge.matterbridgeSessionInformations.values());
1163
- this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
1164
- const response = { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
1165
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
1025
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: await this.getApiSettings() }));
1166
1026
  return;
1167
1027
  }
1168
1028
  else if (data.method === '/api/plugins') {
1169
- const response = await this.getBaseRegisteredPlugins();
1029
+ const response = this.getBaseRegisteredPlugins();
1170
1030
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
1171
1031
  return;
1172
1032
  }
1173
1033
  else if (data.method === '/api/devices') {
1174
1034
  const devices = [];
1175
1035
  this.matterbridge.devices.forEach(async (device) => {
1176
- // Filter by pluginName if provided
1177
1036
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1178
1037
  return;
1179
- // Check if the device has the required properties
1180
1038
  if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
1181
1039
  return;
1182
1040
  const cluster = this.getClusterTextFromDevice(device);
@@ -1220,8 +1078,11 @@ export class Frontend {
1220
1078
  const clusterServers = endpointServer.getAllClusterServers();
1221
1079
  clusterServers.forEach((clusterServer) => {
1222
1080
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
1223
- if (clusterServer.name === 'EveHistory')
1224
- return;
1081
+ if (clusterServer.name === 'EveHistory') {
1082
+ if (['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(key)) {
1083
+ return;
1084
+ }
1085
+ }
1225
1086
  if (clusterServer.name === 'Descriptor' && key === 'deviceTypeList') {
1226
1087
  value.getLocal().forEach((deviceType) => {
1227
1088
  deviceTypes.push(deviceType.deviceType);
@@ -1256,7 +1117,6 @@ export class Frontend {
1256
1117
  });
1257
1118
  endpointServer.getChildEndpoints().forEach((childEndpoint) => {
1258
1119
  deviceTypes = [];
1259
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1260
1120
  const name = childEndpoint.endpoint?.id;
1261
1121
  const clusterServers = childEndpoint.getAllClusterServers();
1262
1122
  clusterServers.forEach((clusterServer) => {
@@ -1339,73 +1199,44 @@ export class Frontend {
1339
1199
  return;
1340
1200
  }
1341
1201
  }
1342
- /**
1343
- * Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
1344
- *
1345
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
1346
- * @param {string} time - The time string of the message
1347
- * @param {string} name - The logger name of the message
1348
- * @param {string} message - The content of the message.
1349
- */
1350
1202
  wssSendMessage(level, time, name, message) {
1351
1203
  if (!level || !time || !name || !message)
1352
1204
  return;
1353
- // Remove ANSI escape codes from the message
1354
- // eslint-disable-next-line no-control-regex
1355
1205
  message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
1356
- // Remove leading asterisks from the message
1357
1206
  message = message.replace(/^\*+/, '');
1358
- // Replace all occurrences of \t and \n
1359
1207
  message = message.replace(/[\t\n]/g, '');
1360
- // Remove non-printable characters
1361
- // eslint-disable-next-line no-control-regex
1362
1208
  message = message.replace(/[\x00-\x1F\x7F]/g, '');
1363
- // Replace all occurrences of \" with "
1364
1209
  message = message.replace(/\\"/g, '"');
1365
- // Define the maximum allowed length for continuous characters without a space
1366
1210
  const maxContinuousLength = 100;
1367
1211
  const keepStartLength = 20;
1368
1212
  const keepEndLength = 20;
1369
- // Split the message into words
1370
1213
  message = message
1371
1214
  .split(' ')
1372
1215
  .map((word) => {
1373
- // If the word length exceeds the max continuous length, insert spaces and truncate
1374
1216
  if (word.length > maxContinuousLength) {
1375
1217
  return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
1376
1218
  }
1377
1219
  return word;
1378
1220
  })
1379
1221
  .join(' ');
1380
- // Send the message to all connected clients
1381
1222
  this.webSocketServer?.clients.forEach((client) => {
1382
1223
  if (client.readyState === WebSocket.OPEN) {
1383
1224
  client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
1384
1225
  }
1385
1226
  });
1386
1227
  }
1387
- /**
1388
- * Sends a need to refresh WebSocket message to all connected clients.
1389
- *
1390
- */
1391
1228
  wssSendRefreshRequired() {
1392
1229
  this.log.debug('Sending a refresh required message to all connected clients');
1393
1230
  this.matterbridge.matterbridgeInformation.refreshRequired = true;
1394
- // Send the message to all connected clients
1395
1231
  this.webSocketServer?.clients.forEach((client) => {
1396
1232
  if (client.readyState === WebSocket.OPEN) {
1397
1233
  client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
1398
1234
  }
1399
1235
  });
1400
1236
  }
1401
- /**
1402
- * Sends a need to restart WebSocket message to all connected clients.
1403
- *
1404
- */
1405
1237
  wssSendRestartRequired() {
1406
1238
  this.log.debug('Sending a restart required message to all connected clients');
1407
1239
  this.matterbridge.matterbridgeInformation.restartRequired = true;
1408
- // Send the message to all connected clients
1409
1240
  this.webSocketServer?.clients.forEach((client) => {
1410
1241
  if (client.readyState === WebSocket.OPEN) {
1411
1242
  client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
@@ -1413,4 +1244,3 @@ export class Frontend {
1413
1244
  });
1414
1245
  }
1415
1246
  }
1416
- //# sourceMappingURL=frontend.js.map