matterbridge 3.1.2-dev-20250705-7da1eac → 3.1.2-dev-20250706-6c6481e

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.
package/CHANGELOG.md CHANGED
@@ -17,6 +17,8 @@ If you like this project and find it useful, please consider giving it a star on
17
17
 
18
18
  ### Added
19
19
 
20
+ - [test]: Improved test units on Frontend class and all Matterbridge classes (coverage 94%).
21
+
20
22
  ### Changed
21
23
 
22
24
  - [package]: Updated dependencies.
package/dist/frontend.js CHANGED
@@ -7,12 +7,12 @@ import EventEmitter from 'node:events';
7
7
  import express from 'express';
8
8
  import WebSocket, { WebSocketServer } from 'ws';
9
9
  import multer from 'multer';
10
- import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from 'node-ansi-logger';
10
+ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
11
11
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
12
12
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
13
13
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
14
14
  import { plg } from './matterbridgeTypes.js';
15
- import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
15
+ import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
16
16
  import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
17
17
  export const WS_ID_LOG = 0;
18
18
  export const WS_ID_REFRESH_NEEDED = 1;
@@ -294,12 +294,12 @@ export class Frontend extends EventEmitter {
294
294
  this.expressApp.get('/api/view-mblog', async (req, res) => {
295
295
  this.log.debug('The frontend sent /api/view-mblog');
296
296
  try {
297
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
297
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
298
298
  res.type('text/plain');
299
299
  res.send(data);
300
300
  }
301
301
  catch (error) {
302
- this.log.error(`Error reading matterbridge log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
302
+ this.log.error(`Error reading matterbridge log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
303
303
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
304
304
  }
305
305
  });
@@ -323,31 +323,31 @@ export class Frontend extends EventEmitter {
323
323
  res.send(data);
324
324
  }
325
325
  catch (error) {
326
- this.log.error(`Error reading shelly log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
326
+ this.log.error(`Error reading shelly log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
327
327
  res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
328
328
  }
329
329
  });
330
330
  this.expressApp.get('/api/download-mblog', async (req, res) => {
331
- this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
331
+ this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
332
332
  try {
333
- await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
334
- const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
335
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
333
+ await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
334
+ const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
335
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
336
336
  }
337
337
  catch (error) {
338
- await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
338
+ await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
339
339
  this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
340
340
  }
341
341
  res.type('text/plain');
342
- res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
342
+ res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
343
343
  if (error) {
344
- this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
344
+ this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
345
345
  res.status(500).send('Error downloading the matterbridge log file');
346
346
  }
347
347
  });
348
348
  });
349
349
  this.expressApp.get('/api/download-mjlog', async (req, res) => {
350
- this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
350
+ this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
351
351
  try {
352
352
  await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
353
353
  const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
@@ -419,8 +419,8 @@ export class Frontend extends EventEmitter {
419
419
  await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
420
420
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
421
421
  if (error) {
422
- this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
423
- res.status(500).send('Error downloading the matterbridge plugin storage file');
422
+ this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
423
+ res.status(500).send('Error downloading the matterbridge plugin config file');
424
424
  }
425
425
  });
426
426
  });
@@ -635,40 +635,23 @@ export class Frontend extends EventEmitter {
635
635
  getClusterTextFromDevice(device) {
636
636
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
637
637
  return '';
638
- const getAttribute = (device, cluster, attribute) => {
639
- let value = undefined;
640
- Object.entries(device.state)
641
- .filter(([clusterName]) => clusterName.toLowerCase() === cluster.toLowerCase())
642
- .forEach(([, clusterAttributes]) => {
643
- Object.entries(clusterAttributes)
644
- .filter(([attributeName]) => attributeName.toLowerCase() === attribute.toLowerCase())
645
- .forEach(([, attributeValue]) => {
646
- value = attributeValue;
647
- });
648
- });
649
- if (value === undefined)
650
- this.log.error(`Cluster ${cluster} or attribute ${attribute} not found in device ${device.deviceName}`);
651
- return value;
652
- };
653
638
  const getUserLabel = (device) => {
654
639
  const labelList = getAttribute(device, 'userLabel', 'labelList');
655
- if (!labelList)
656
- return;
657
- const composed = labelList.find((entry) => entry.label === 'composed');
658
- if (composed)
659
- return 'Composed: ' + composed.value;
660
- else
661
- return '';
640
+ if (labelList) {
641
+ const composed = labelList.find((entry) => entry.label === 'composed');
642
+ if (composed)
643
+ return 'Composed: ' + composed.value;
644
+ }
645
+ return '';
662
646
  };
663
647
  const getFixedLabel = (device) => {
664
648
  const labelList = getAttribute(device, 'fixedLabel', 'labelList');
665
- if (!labelList)
666
- return;
667
- const composed = labelList.find((entry) => entry.label === 'composed');
668
- if (composed)
669
- return 'Composed: ' + composed.value;
670
- else
671
- return '';
649
+ if (labelList) {
650
+ const composed = labelList.find((entry) => entry.label === 'composed');
651
+ if (composed)
652
+ return 'Composed: ' + composed.value;
653
+ }
654
+ return '';
672
655
  };
673
656
  let attributes = '';
674
657
  let supportedModes = [];
@@ -697,8 +680,6 @@ export class Frontend extends EventEmitter {
697
680
  const supportedMode = supportedModes.find((mode) => mode.mode === attributeValue);
698
681
  if (supportedMode)
699
682
  attributes += `Mode: ${supportedMode.label} `;
700
- else
701
- attributes += `Mode: ${attributeValue} `;
702
683
  }
703
684
  const operationalStateClusters = ['operationalState', 'rvcOperationalState'];
704
685
  if (operationalStateClusters.includes(clusterName) && attributeName === 'operationalState')
@@ -1102,10 +1083,7 @@ export class Frontend extends EventEmitter {
1102
1083
  }
1103
1084
  this.log.info(`Saving config for plugin ${plg}${data.params.pluginName}${nf}...`);
1104
1085
  const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1105
- if (!plugin) {
1106
- this.log.warn(`Plugin ${plg}${data.params.pluginName}${wr} not found in matterbridge`);
1107
- }
1108
- else {
1086
+ if (plugin) {
1109
1087
  this.matterbridge.plugins.saveConfigFromJson(plugin, data.params.formData, true);
1110
1088
  this.wssSendSnackbarMessage(`Saved config for plugin ${data.params.pluginName}`);
1111
1089
  this.wssSendRefreshRequired('pluginsRestart');
@@ -1191,7 +1169,7 @@ export class Frontend extends EventEmitter {
1191
1169
  this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
1192
1170
  this.wssSendRefreshRequired('matterbridgeAdvertise');
1193
1171
  this.wssSendSnackbarMessage(`Started fabrics share`, 0);
1194
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes }));
1172
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
1195
1173
  }
1196
1174
  else if (data.method === '/api/stopadvertise') {
1197
1175
  await this.matterbridge.stopAdvertiseServerNode(this.matterbridge.serverNode);
@@ -1338,7 +1316,7 @@ export class Frontend extends EventEmitter {
1338
1316
  this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1339
1317
  await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1340
1318
  if (data.params.value)
1341
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1319
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1342
1320
  else
1343
1321
  AnsiLogger.setGlobalLogfile(undefined);
1344
1322
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
@@ -1450,14 +1428,15 @@ export class Frontend extends EventEmitter {
1450
1428
  this.matterbridge.matterbridgeInformation.matterDiscriminator = data.params.value;
1451
1429
  await this.matterbridge.nodeContext?.set('matterdiscriminator', data.params.value);
1452
1430
  this.wssSendRestartRequired();
1431
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1453
1432
  }
1454
1433
  else {
1455
1434
  this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
1456
1435
  this.matterbridge.matterbridgeInformation.matterDiscriminator = undefined;
1457
1436
  await this.matterbridge.nodeContext?.remove('matterdiscriminator');
1458
1437
  this.wssSendRestartRequired();
1438
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: false }));
1459
1439
  }
1460
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1461
1440
  break;
1462
1441
  case 'setmatterpasscode':
1463
1442
  data.params.value = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
@@ -1466,14 +1445,15 @@ export class Frontend extends EventEmitter {
1466
1445
  this.log.debug(`Set matter commissioning passcode to ${CYAN}${data.params.value}${db}`);
1467
1446
  await this.matterbridge.nodeContext?.set('matterpasscode', data.params.value);
1468
1447
  this.wssSendRestartRequired();
1448
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1469
1449
  }
1470
1450
  else {
1471
1451
  this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
1472
1452
  this.matterbridge.matterbridgeInformation.matterPasscode = undefined;
1473
1453
  await this.matterbridge.nodeContext?.remove('matterpasscode');
1474
1454
  this.wssSendRestartRequired();
1455
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: false }));
1475
1456
  }
1476
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
1477
1457
  break;
1478
1458
  case 'setvirtualmode':
1479
1459
  if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
@@ -99,7 +99,7 @@ export class Matterbridge extends EventEmitter {
99
99
  edge = true;
100
100
  failCountLimit = hasParameter('shelly') ? 600 : 120;
101
101
  log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
102
- matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
102
+ matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
103
103
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
104
104
  plugins;
105
105
  devices;
@@ -346,7 +346,7 @@ export class Matterbridge extends EventEmitter {
346
346
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
347
347
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
348
348
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
349
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
349
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
350
350
  this.matterbridgeInformation.fileLogger = true;
351
351
  }
352
352
  this.log.notice('Matterbridge is starting...');
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.1.2-dev-20250705-7da1eac",
3
+ "version": "3.1.2-dev-20250706-6c6481e",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.1.2-dev-20250705-7da1eac",
9
+ "version": "3.1.2-dev-20250706-6c6481e",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.15.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.1.2-dev-20250705-7da1eac",
3
+ "version": "3.1.2-dev-20250706-6c6481e",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",