matterbridge 3.0.5-dev-20250609-a5046db → 3.0.6-dev-20250610-56cd483

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
@@ -8,6 +8,24 @@ If you like this project and find it useful, please consider giving it a star on
8
8
  <img src="bmc-button.svg" alt="Buy me a coffee" width="120">
9
9
  </a>
10
10
 
11
+ ## [3.0.6] - 2025-06-??
12
+
13
+ ### Added
14
+
15
+ - [tests] Update Jest test coverage on addBridgedEndpoint and removeBridgedEndpoint.
16
+
17
+ ### Changed
18
+
19
+ - [package]: Updated dependencies.
20
+
21
+ ### Fixed
22
+
23
+ - [evse]: Fixed jsdoc on Evse.
24
+
25
+ <a href="https://www.buymeacoffee.com/luligugithub">
26
+ <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
27
+ </a>
28
+
11
29
  ## [3.0.5] - 2025-06-07
12
30
 
13
31
  ### Added
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Docker Version](https://img.shields.io/docker/v/luligu/matterbridge?label=docker%20version&sort=semver)](https://hub.docker.com/r/luligu/matterbridge)
6
6
  [![Docker Pulls](https://img.shields.io/docker/pulls/luligu/matterbridge.svg)](https://hub.docker.com/r/luligu/matterbridge)
7
7
  ![Node.js CI](https://github.com/Luligu/matterbridge/actions/workflows/build.yml/badge.svg)
8
- ![Coverage](https://img.shields.io/badge/Jest%20coverage-86%25-brightgreen)
8
+ ![Coverage](https://img.shields.io/badge/Jest%20coverage-88%25-brightgreen)
9
9
 
10
10
  [![power by](https://img.shields.io/badge/powered%20by-matter--history-blue)](https://www.npmjs.com/package/matter-history)
11
11
  [![power by](https://img.shields.io/badge/powered%20by-node--ansi--logger-blue)](https://www.npmjs.com/package/node-ansi-logger)
package/dist/frontend.js CHANGED
@@ -13,6 +13,7 @@ import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, i
13
13
  import { plg } from './matterbridgeTypes.js';
14
14
  import { hasParameter } from './utils/export.js';
15
15
  import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
16
+ import spawn from './utils/spawn.js';
16
17
  export const WS_ID_LOG = 0;
17
18
  export const WS_ID_REFRESH_NEEDED = 1;
18
19
  export const WS_ID_RESTART_NEEDED = 2;
@@ -423,7 +424,7 @@ export class Frontend {
423
424
  await fs.rename(file.path, filePath);
424
425
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
425
426
  if (filename.endsWith('.tgz')) {
426
- await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
427
+ await spawn.spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
427
428
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
428
429
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
429
430
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
@@ -884,8 +885,8 @@ export class Frontend {
884
885
  return;
885
886
  }
886
887
  this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
887
- this.matterbridge
888
- .spawnCommand('npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
888
+ spawn
889
+ .spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
889
890
  .then((response) => {
890
891
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
891
892
  this.wssSendCloseSnackbarMessage(`Installing package ${data.params.packageName}...`);
@@ -938,8 +939,8 @@ export class Frontend {
938
939
  this.wssSendRefreshRequired('devices');
939
940
  }
940
941
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
941
- this.matterbridge
942
- .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
942
+ spawn
943
+ .spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
943
944
  .then((response) => {
944
945
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
945
946
  this.wssSendCloseSnackbarMessage(`Uninstalling package ${data.params.packageName}...`);
@@ -5,7 +5,7 @@ import EventEmitter from 'node:events';
5
5
  import { inspect } from 'node:util';
6
6
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
7
7
  import { NodeStorageManager } from './storage/export.js';
8
- import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber } from './utils/export.js';
8
+ import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
9
9
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
10
10
  import { dev, plg, typ } from './matterbridgeTypes.js';
11
11
  import { PluginManager } from './pluginManager.js';
@@ -14,6 +14,7 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
14
14
  import { bridge } from './matterbridgeDeviceTypes.js';
15
15
  import { Frontend } from './frontend.js';
16
16
  import { addVirtualDevices } from './helpers.js';
17
+ import spawn from './utils/spawn.js';
17
18
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
18
19
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
19
20
  import { AggregatorEndpoint } from '@matter/main/endpoints';
@@ -116,6 +117,7 @@ export class Matterbridge extends EventEmitter {
116
117
  checkUpdateTimeout;
117
118
  configureTimeout;
118
119
  reachabilityTimeout;
120
+ endAdvertiseTimeout;
119
121
  sigintHandler;
120
122
  sigtermHandler;
121
123
  exceptionHandler;
@@ -221,18 +223,18 @@ export class Matterbridge extends EventEmitter {
221
223
  this.restartMode = 'docker';
222
224
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
223
225
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
224
- await this.createDirectory(this.homeDirectory, 'Matterbridge Home Directory');
226
+ await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
225
227
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
226
228
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
227
- await this.createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory');
228
- await this.createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory');
229
- await this.createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory');
229
+ await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
230
+ await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
231
+ await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
230
232
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
231
233
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
232
- await this.createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory');
234
+ await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
233
235
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
234
236
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
235
- await this.createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory');
237
+ await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
236
238
  const { fileURLToPath } = await import('node:url');
237
239
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
238
240
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
@@ -502,7 +504,7 @@ export class Matterbridge extends EventEmitter {
502
504
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
503
505
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
504
506
  try {
505
- await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
507
+ await spawn.spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
506
508
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
507
509
  plugin.error = false;
508
510
  }
@@ -989,7 +991,7 @@ export class Matterbridge extends EventEmitter {
989
991
  async updateProcess() {
990
992
  this.log.info('Updating matterbridge...');
991
993
  try {
992
- await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
994
+ await spawn.spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
993
995
  this.log.info('Matterbridge has been updated. Full restart required.');
994
996
  }
995
997
  catch (error) {
@@ -1338,19 +1340,6 @@ export class Matterbridge extends EventEmitter {
1338
1340
  }, 1000);
1339
1341
  }
1340
1342
  async startController() {
1341
- if (!this.matterStorageManager) {
1342
- this.log.error('No storage manager initialized');
1343
- await this.cleanup('No storage manager initialized');
1344
- return;
1345
- }
1346
- this.log.info('Creating context: mattercontrollerContext');
1347
- this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1348
- if (!this.controllerContext) {
1349
- this.log.error('No storage context mattercontrollerContext initialized');
1350
- await this.cleanup('No storage context mattercontrollerContext initialized');
1351
- return;
1352
- }
1353
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1354
1343
  }
1355
1344
  async startMatterStorage() {
1356
1345
  this.log.info(`Starting matter node storage...`);
@@ -1365,8 +1354,13 @@ export class Matterbridge extends EventEmitter {
1365
1354
  }
1366
1355
  async backupMatterStorage(storageName, backupName) {
1367
1356
  this.log.info('Creating matter node storage backup...');
1368
- await copyDirectory(storageName, backupName);
1369
- this.log.info('Created matter node storage backup');
1357
+ try {
1358
+ await copyDirectory(storageName, backupName);
1359
+ this.log.info('Created matter node storage backup');
1360
+ }
1361
+ catch (error) {
1362
+ this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1363
+ }
1370
1364
  }
1371
1365
  async stopMatterStorage() {
1372
1366
  this.log.info('Closing matter node storage...');
@@ -1471,7 +1465,10 @@ export class Matterbridge extends EventEmitter {
1471
1465
  }
1472
1466
  }
1473
1467
  };
1474
- serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1468
+ serverNode.lifecycle.commissioned.on(() => {
1469
+ this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1470
+ clearTimeout(this.endAdvertiseTimeout);
1471
+ });
1475
1472
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1476
1473
  serverNode.lifecycle.online.on(async () => {
1477
1474
  this.log.notice(`Server node for ${storeId} is online`);
@@ -1511,27 +1508,7 @@ export class Matterbridge extends EventEmitter {
1511
1508
  }
1512
1509
  }
1513
1510
  }
1514
- setTimeout(() => {
1515
- if (serverNode.lifecycle.isCommissioned)
1516
- return;
1517
- if (this.bridgeMode === 'bridge') {
1518
- this.matterbridgeQrPairingCode = undefined;
1519
- this.matterbridgeManualPairingCode = undefined;
1520
- }
1521
- if (this.bridgeMode === 'childbridge') {
1522
- const plugin = this.plugins.get(storeId);
1523
- if (plugin) {
1524
- plugin.qrPairingCode = undefined;
1525
- plugin.manualPairingCode = undefined;
1526
- }
1527
- }
1528
- this.frontend.wssSendRefreshRequired('plugins');
1529
- this.frontend.wssSendRefreshRequired('settings');
1530
- this.frontend.wssSendRefreshRequired('fabrics');
1531
- this.frontend.wssSendRefreshRequired('sessions');
1532
- this.frontend.wssSendSnackbarMessage(`Advertising on server node for ${storeId} stopped. Restart to commission.`, 0);
1533
- this.log.notice(`Advertising on server node for ${storeId} stopped. Restart to commission.`);
1534
- }, 15 * 60 * 1000).unref();
1511
+ this.startEndAdvertiseTimer(serverNode);
1535
1512
  }
1536
1513
  else {
1537
1514
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
@@ -1540,6 +1517,7 @@ export class Matterbridge extends EventEmitter {
1540
1517
  this.frontend.wssSendRefreshRequired('plugins');
1541
1518
  this.frontend.wssSendRefreshRequired('settings');
1542
1519
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1520
+ this.emit('online', storeId);
1543
1521
  });
1544
1522
  serverNode.lifecycle.offline.on(() => {
1545
1523
  this.log.notice(`Server node for ${storeId} is offline`);
@@ -1563,6 +1541,7 @@ export class Matterbridge extends EventEmitter {
1563
1541
  this.frontend.wssSendRefreshRequired('plugins');
1564
1542
  this.frontend.wssSendRefreshRequired('settings');
1565
1543
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1544
+ this.emit('offline', storeId);
1566
1545
  });
1567
1546
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1568
1547
  let action = '';
@@ -1615,18 +1594,46 @@ export class Matterbridge extends EventEmitter {
1615
1594
  this.log.info(`Created server node for ${storeId}`);
1616
1595
  return serverNode;
1617
1596
  }
1597
+ startEndAdvertiseTimer(matterServerNode) {
1598
+ if (this.endAdvertiseTimeout) {
1599
+ this.log.debug(`***Clear ${matterServerNode.id} server node end advertise timer`);
1600
+ clearTimeout(this.endAdvertiseTimeout);
1601
+ }
1602
+ this.log.debug(`***Starting ${matterServerNode.id} server node end advertise timer`);
1603
+ this.endAdvertiseTimeout = setTimeout(() => {
1604
+ if (matterServerNode.lifecycle.isCommissioned)
1605
+ return;
1606
+ if (this.bridgeMode === 'bridge') {
1607
+ this.matterbridgeQrPairingCode = undefined;
1608
+ this.matterbridgeManualPairingCode = undefined;
1609
+ }
1610
+ if (this.bridgeMode === 'childbridge') {
1611
+ const plugin = this.plugins.get(matterServerNode.id);
1612
+ if (plugin) {
1613
+ plugin.qrPairingCode = undefined;
1614
+ plugin.manualPairingCode = undefined;
1615
+ }
1616
+ }
1617
+ this.frontend.wssSendRefreshRequired('plugins');
1618
+ this.frontend.wssSendRefreshRequired('settings');
1619
+ this.frontend.wssSendRefreshRequired('fabrics');
1620
+ this.frontend.wssSendRefreshRequired('sessions');
1621
+ this.frontend.wssSendSnackbarMessage(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`, 0);
1622
+ this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
1623
+ }, 15 * 60 * 1000).unref();
1624
+ }
1618
1625
  async startServerNode(matterServerNode) {
1619
1626
  if (!matterServerNode)
1620
1627
  return;
1621
1628
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1622
1629
  await matterServerNode.start();
1623
1630
  }
1624
- async stopServerNode(matterServerNode) {
1631
+ async stopServerNode(matterServerNode, timeout = 30000) {
1625
1632
  if (!matterServerNode)
1626
1633
  return;
1627
1634
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1628
1635
  try {
1629
- await withTimeout(matterServerNode.close(), 30000);
1636
+ await withTimeout(matterServerNode.close(), timeout);
1630
1637
  this.log.info(`Closed ${matterServerNode.id} server node`);
1631
1638
  }
1632
1639
  catch (error) {
@@ -1864,89 +1871,4 @@ export class Matterbridge extends EventEmitter {
1864
1871
  }
1865
1872
  return vendorName;
1866
1873
  };
1867
- async spawnCommand(command, args = []) {
1868
- const { spawn } = await import('node:child_process');
1869
- const cmdLine = command + ' ' + args.join(' ');
1870
- if (process.platform === 'win32' && command === 'npm') {
1871
- const argstring = 'npm ' + args.join(' ');
1872
- args.splice(0, args.length, '/c', argstring);
1873
- command = 'cmd.exe';
1874
- }
1875
- if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1876
- args.unshift(command);
1877
- command = 'sudo';
1878
- }
1879
- this.log.debug(`Spawn command ${command} with ${debugStringify(args)}`);
1880
- return new Promise((resolve, reject) => {
1881
- const childProcess = spawn(command, args, {
1882
- stdio: ['inherit', 'pipe', 'pipe'],
1883
- });
1884
- childProcess.on('error', (err) => {
1885
- this.log.error(`Failed to start child process "${cmdLine}": ${err.message}`);
1886
- reject(err);
1887
- });
1888
- childProcess.on('close', (code, signal) => {
1889
- this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
1890
- if (code === 0) {
1891
- if (cmdLine.startsWith('npm install -g'))
1892
- this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
1893
- this.log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
1894
- resolve(true);
1895
- }
1896
- else {
1897
- this.log.error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
1898
- reject(new Error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`));
1899
- }
1900
- });
1901
- childProcess.on('exit', (code, signal) => {
1902
- this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
1903
- if (code === 0) {
1904
- this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
1905
- resolve(true);
1906
- }
1907
- else {
1908
- this.log.error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
1909
- reject(new Error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`));
1910
- }
1911
- });
1912
- childProcess.on('disconnect', () => {
1913
- this.log.debug(`Child process "${cmdLine}" has been disconnected from the parent`);
1914
- resolve(true);
1915
- });
1916
- if (childProcess.stdout) {
1917
- childProcess.stdout.on('data', (data) => {
1918
- const message = data.toString().trim();
1919
- this.log.debug(`Spawn output (stdout): ${message}`);
1920
- this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
1921
- });
1922
- }
1923
- if (childProcess.stderr) {
1924
- childProcess.stderr.on('data', (data) => {
1925
- const message = data.toString().trim();
1926
- this.log.debug(`Spawn verbose (stderr): ${message}`);
1927
- this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
1928
- });
1929
- }
1930
- });
1931
- }
1932
- async createDirectory(path, name) {
1933
- try {
1934
- await fs.access(path);
1935
- this.log.debug(`Directory ${name} already exists at path: ${path}`);
1936
- }
1937
- catch (err) {
1938
- if (err.code === 'ENOENT') {
1939
- try {
1940
- await fs.mkdir(path, { recursive: true });
1941
- this.log.info(`Created ${name}: ${path}`);
1942
- }
1943
- catch (err) {
1944
- this.log.error(`Error creating dir ${name} path ${path}: ${err}`);
1945
- }
1946
- }
1947
- else {
1948
- this.log.error(`Error accessing dir ${name} path ${path}: ${err}`);
1949
- }
1950
- }
1951
- }
1952
1874
  }
@@ -750,14 +750,8 @@ export class PluginManager {
750
750
  this.log.debug(`Loaded schema file ${schemaFile} for plugin ${plg}${plugin.name}${db}.`);
751
751
  return schema;
752
752
  }
753
- catch (err) {
754
- const nodeErr = err;
755
- if (nodeErr.code === 'ENOENT') {
756
- this.log.debug(`Schema file ${schemaFile} for plugin ${plg}${plugin.name}${db} not found. Loading default schema.`);
757
- }
758
- else {
759
- this.log.debug(`Schema file ${schemaFile} for plugin ${plg}${plugin.name}${db} not found. Loading default schema. Error: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
760
- }
753
+ catch (_err) {
754
+ this.log.debug(`Schema file ${schemaFile} for plugin ${plg}${plugin.name}${db} not found. Loading default schema.`);
761
755
  return this.getDefaultSchema(plugin);
762
756
  }
763
757
  }
package/dist/shelly.js CHANGED
@@ -70,7 +70,7 @@ export async function triggerShellyMainUpdate(matterbridge) {
70
70
  matterbridge.log.error(`Error triggering Shelly main update: ${err instanceof Error ? err.message : String(err)}`);
71
71
  }
72
72
  }
73
- async function verifyShellyUpdate(matterbridge, api, name) {
73
+ export async function verifyShellyUpdate(matterbridge, api, name) {
74
74
  return new Promise((resolve) => {
75
75
  const timeout = setTimeout(() => {
76
76
  matterbridge.log.error(`${name} check timed out`);
@@ -1,5 +1,20 @@
1
1
  import { AnsiLogger } from '../logger/export.js';
2
2
  export async function copyDirectory(srcDir, destDir) {
3
+ if (srcDir === '') {
4
+ throw new Error('Source directory must be specified.');
5
+ }
6
+ if (destDir === '') {
7
+ throw new Error('Destination directory must be specified.');
8
+ }
9
+ if (!srcDir) {
10
+ throw new Error('Source directory must be specified.');
11
+ }
12
+ if (!destDir) {
13
+ throw new Error('Destination directory must be specified.');
14
+ }
15
+ if (srcDir === destDir) {
16
+ throw new Error('Source and destination directories must be different.');
17
+ }
3
18
  const log = new AnsiLogger({ logName: 'Archive', logTimestampFormat: 4, logLevel: "info" });
4
19
  const fs = await import('node:fs').then((mod) => mod.promises);
5
20
  const path = await import('node:path');
@@ -0,0 +1,21 @@
1
+ import { promises as fs } from 'node:fs';
2
+ export async function createDirectory(path, name, log) {
3
+ try {
4
+ await fs.access(path);
5
+ log.debug(`Directory ${name} already exists at path: ${path}`);
6
+ }
7
+ catch (err) {
8
+ if (err.code === 'ENOENT') {
9
+ try {
10
+ await fs.mkdir(path, { recursive: true });
11
+ log.info(`Created ${name}: ${path}`);
12
+ }
13
+ catch (err) {
14
+ log.error(`Error creating dir ${name} path ${path}: ${err}`);
15
+ }
16
+ }
17
+ else {
18
+ log.error(`Error accessing dir ${name} path ${path}: ${err}`);
19
+ }
20
+ }
21
+ }
@@ -5,6 +5,7 @@ export * from './colorUtils.js';
5
5
  export * from './deepCopy.js';
6
6
  export * from './deepEqual.js';
7
7
  export * from './copyDirectory.js';
8
+ export * from './createDirectory.js';
8
9
  export * from './createZip.js';
9
10
  export * from './wait.js';
10
11
  export * from './hex.js';
@@ -0,0 +1,68 @@
1
+ import { hasParameter } from './commandLine.js';
2
+ export default {
3
+ async spawnCommand(matterbridge, command, args) {
4
+ const { spawn } = await import('node:child_process');
5
+ const cmdLine = command + ' ' + args.join(' ');
6
+ if (process.platform === 'win32' && command === 'npm') {
7
+ const argstring = 'npm ' + args.join(' ');
8
+ args.splice(0, args.length, '/c', argstring);
9
+ command = 'cmd.exe';
10
+ }
11
+ if (hasParameter('sudo') || (process.platform !== 'win32' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
12
+ args.unshift(command);
13
+ command = 'sudo';
14
+ }
15
+ matterbridge.log.debug(`Spawn command ${command} with ${args.join(' ')}`);
16
+ return new Promise((resolve, reject) => {
17
+ const childProcess = spawn(command, args, {
18
+ stdio: ['inherit', 'pipe', 'pipe'],
19
+ });
20
+ childProcess.on('error', (err) => {
21
+ matterbridge.log.error(`Failed to start child process "${cmdLine}": ${err.message}`);
22
+ reject(err);
23
+ });
24
+ childProcess.on('close', (code, signal) => {
25
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
26
+ if (code === 0) {
27
+ if (cmdLine.startsWith('npm install -g'))
28
+ matterbridge.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
29
+ matterbridge.log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
30
+ resolve(true);
31
+ }
32
+ else {
33
+ matterbridge.log.error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
34
+ reject(new Error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`));
35
+ }
36
+ });
37
+ childProcess.on('exit', (code, signal) => {
38
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
39
+ if (code === 0) {
40
+ matterbridge.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
41
+ resolve(true);
42
+ }
43
+ else {
44
+ matterbridge.log.error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
45
+ reject(new Error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`));
46
+ }
47
+ });
48
+ childProcess.on('disconnect', () => {
49
+ matterbridge.log.debug(`Child process "${cmdLine}" has been disconnected from the parent`);
50
+ resolve(true);
51
+ });
52
+ if (childProcess.stdout) {
53
+ childProcess.stdout.on('data', (data) => {
54
+ const message = data.toString().trim();
55
+ matterbridge.log.debug(`Spawn output (stdout): ${message}`);
56
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', message);
57
+ });
58
+ }
59
+ if (childProcess.stderr) {
60
+ childProcess.stderr.on('data', (data) => {
61
+ const message = data.toString().trim();
62
+ matterbridge.log.debug(`Spawn verbose (stderr): ${message}`);
63
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', message);
64
+ });
65
+ }
66
+ });
67
+ },
68
+ };
@@ -18,7 +18,7 @@ export async function waiter(name, check, exitWithReject = false, resolveTimeout
18
18
  else
19
19
  resolve(false);
20
20
  }, resolveTimeout);
21
- const intervalId = setInterval(() => {
21
+ const intervalId = setInterval(async () => {
22
22
  if (check()) {
23
23
  if (debug)
24
24
  log.debug(`Waiter "${name}" finished for true condition...`);
@@ -26,6 +26,7 @@ export async function waiter(name, check, exitWithReject = false, resolveTimeout
26
26
  clearInterval(intervalId);
27
27
  resolve(true);
28
28
  }
29
+ await Promise.resolve();
29
30
  }, resolveInterval);
30
31
  });
31
32
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.5-dev-20250609-a5046db",
3
+ "version": "3.0.6-dev-20250610-56cd483",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.0.5-dev-20250609-a5046db",
9
+ "version": "3.0.6-dev-20250610-56cd483",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.14.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.5-dev-20250609-a5046db",
3
+ "version": "3.0.6-dev-20250610-56cd483",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",