matterbridge 3.1.1-dev-20250701-9281629 → 3.1.1-dev-20250704-aff5fcb

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,11 +8,12 @@ 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.1.1] - 2025-07-01
11
+ ## [3.1.1] - 2025-07-08
12
12
 
13
13
  ### Development Breaking Changes
14
14
 
15
- - [devices]: The single devices (i.e. Rvc, Evse etc...) are only exported from matterbridge/devices. Please update your imports to use the new export path. Refer to the [documentation](README-DEV.md) for details on imports.
15
+ - [exports]: The single devices (i.e. Rvc, Evse etc...) are only exported from `matterbridge/devices`. Please update your imports to use the new export path. Refer to the [documentation](README-DEV.md) for details on imports.
16
+ - [MatterbridgeEndpoint]: Added the mode property: `server` will make the device indipendent from its plugin. It has its own server node: QRCode, Fabrics and Sessions are visible in the Devices section of the Home page. This is a workaround for the Rvc Apple issue. With mode=server the Rvc (like any other device) can be paired directly to the controller like a native not bridged Matter device.
16
17
 
17
18
  ### Added
18
19
 
@@ -26,8 +27,9 @@ If you like this project and find it useful, please consider giving it a star on
26
27
 
27
28
  - [package]: Updated dependencies.
28
29
  - [frontend]: Added all esa devices.
29
- - [frontend]: New default values: select on the home page and icon view on devices page.
30
+ - [frontend]: New default values: devices on the home page and icon view on the devices page.
30
31
  - [matter.js]: Bumped `matter.js` to 0.15.1 (https://github.com/project-chip/matter.js/discussions/2220). Great job matter.js!
32
+ - [imports]: Added more dynamic imports to Matterbridge and Frontend classes.
31
33
 
32
34
  <a href="https://www.buymeacoffee.com/luligugithub">
33
35
  <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
package/dist/frontend.js CHANGED
@@ -3,6 +3,7 @@ import https from 'node:https';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { promises as fs } from 'node:fs';
6
+ import EventEmitter from 'node:events';
6
7
  import express from 'express';
7
8
  import WebSocket, { WebSocketServer } from 'ws';
8
9
  import multer from 'multer';
@@ -12,7 +13,6 @@ import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/cluster
12
13
  import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
13
14
  import { plg } from './matterbridgeTypes.js';
14
15
  import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
15
- import spawn from './utils/spawn.js';
16
16
  export const WS_ID_LOG = 0;
17
17
  export const WS_ID_REFRESH_NEEDED = 1;
18
18
  export const WS_ID_RESTART_NEEDED = 2;
@@ -25,7 +25,7 @@ export const WS_ID_STATEUPDATE = 8;
25
25
  export const WS_ID_CLOSE_SNACKBAR = 9;
26
26
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
27
27
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
28
- export class Frontend {
28
+ export class Frontend extends EventEmitter {
29
29
  matterbridge;
30
30
  log;
31
31
  port = 8283;
@@ -35,6 +35,7 @@ export class Frontend {
35
35
  httpsServer;
36
36
  webSocketServer;
37
37
  constructor(matterbridge) {
38
+ super();
38
39
  this.matterbridge = matterbridge;
39
40
  this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
40
41
  }
@@ -54,6 +55,7 @@ export class Frontend {
54
55
  if (hasParameter('ingress')) {
55
56
  this.httpServer.listen(this.port, '0.0.0.0', () => {
56
57
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
58
+ this.emit('server_listening', 'http', this.port, '0.0.0.0');
57
59
  });
58
60
  }
59
61
  else {
@@ -62,6 +64,7 @@ export class Frontend {
62
64
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
63
65
  if (this.matterbridge.systemInformation.ipv6Address !== '')
64
66
  this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
67
+ this.emit('server_listening', 'http', this.port);
65
68
  });
66
69
  }
67
70
  this.httpServer.on('error', (error) => {
@@ -75,6 +78,7 @@ export class Frontend {
75
78
  break;
76
79
  }
77
80
  this.initializeError = true;
81
+ this.emit('server_error', error);
78
82
  return;
79
83
  });
80
84
  }
@@ -110,6 +114,7 @@ export class Frontend {
110
114
  if (hasParameter('ingress')) {
111
115
  this.httpsServer.listen(this.port, '0.0.0.0', () => {
112
116
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
117
+ this.emit('server_listening', 'https', this.port, '0.0.0.0');
113
118
  });
114
119
  }
115
120
  else {
@@ -118,6 +123,7 @@ export class Frontend {
118
123
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
119
124
  if (this.matterbridge.systemInformation.ipv6Address !== '')
120
125
  this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
126
+ this.emit('server_listening', 'https', this.port);
121
127
  });
122
128
  }
123
129
  this.httpsServer.on('error', (error) => {
@@ -131,6 +137,7 @@ export class Frontend {
131
137
  break;
132
138
  }
133
139
  this.initializeError = true;
140
+ this.emit('server_error', error);
134
141
  return;
135
142
  });
136
143
  }
@@ -175,6 +182,7 @@ export class Frontend {
175
182
  });
176
183
  this.webSocketServer.on('listening', () => {
177
184
  this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
185
+ this.emit('websocket_server_listening', wssHost);
178
186
  });
179
187
  this.webSocketServer.on('error', (ws, error) => {
180
188
  this.log.error(`WebSocketServer error: ${error}`);
@@ -423,7 +431,8 @@ export class Frontend {
423
431
  await fs.rename(file.path, filePath);
424
432
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
425
433
  if (filename.endsWith('.tgz')) {
426
- await spawn.spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
434
+ const { spawnCommand } = await import('./utils/spawn.js');
435
+ await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
427
436
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
428
437
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
429
438
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
@@ -566,6 +575,8 @@ export class Frontend {
566
575
  return false;
567
576
  if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
568
577
  return device.getAttribute(BridgedDeviceBasicInformation.Cluster.id, 'reachable');
578
+ if (device.mode === 'server' && device.serverNode && device.serverNode.state.basicInformation.reachable !== undefined)
579
+ return device.serverNode.state.basicInformation.reachable;
569
580
  if (this.matterbridge.bridgeMode === 'childbridge')
570
581
  return true;
571
582
  return false;
@@ -592,6 +603,17 @@ export class Frontend {
592
603
  return powerSource(child);
593
604
  }
594
605
  }
606
+ getMatterDataFromDevice(device) {
607
+ if (device.mode === 'server' && device.serverNode && device.serverContext) {
608
+ return {
609
+ commissioned: device.serverNode.state.commissioning.commissioned,
610
+ qrPairingCode: device.serverNode.state.commissioning.pairingCodes.qrPairingCode,
611
+ manualPairingCode: device.serverNode.state.commissioning.pairingCodes.manualPairingCode,
612
+ fabricInformations: this.matterbridge.sanitizeFabricInformations(Object.values(device.serverNode.state.commissioning.fabrics)),
613
+ sessionInformations: this.matterbridge.sanitizeSessionInformation(Object.values(device.serverNode.state.sessions.sessions)),
614
+ };
615
+ }
616
+ }
595
617
  getClusterTextFromDevice(device) {
596
618
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
597
619
  return '';
@@ -766,12 +788,11 @@ export class Frontend {
766
788
  }
767
789
  async getDevices(pluginName) {
768
790
  const devices = [];
769
- this.matterbridge.devices.forEach(async (device) => {
791
+ for (const device of this.matterbridge.devices.array()) {
770
792
  if (pluginName && pluginName !== device.plugin)
771
- return;
793
+ continue;
772
794
  if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
773
- return;
774
- const cluster = this.getClusterTextFromDevice(device);
795
+ continue;
775
796
  devices.push({
776
797
  pluginName: device.plugin,
777
798
  type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
@@ -783,9 +804,10 @@ export class Frontend {
783
804
  uniqueId: device.uniqueId,
784
805
  reachable: this.getReachability(device),
785
806
  powerSource: this.getPowerSource(device),
786
- cluster: cluster,
807
+ matter: this.getMatterDataFromDevice(device),
808
+ cluster: this.getClusterTextFromDevice(device),
787
809
  });
788
- });
810
+ }
789
811
  return devices;
790
812
  }
791
813
  getClusters(pluginName, endpointNumber) {
@@ -884,8 +906,8 @@ export class Frontend {
884
906
  return;
885
907
  }
886
908
  this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
887
- spawn
888
- .spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
909
+ const { spawnCommand } = await import('./utils/spawn.js');
910
+ spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
889
911
  .then((response) => {
890
912
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
891
913
  this.wssSendCloseSnackbarMessage(`Installing package ${data.params.packageName}...`);
@@ -949,8 +971,8 @@ export class Frontend {
949
971
  this.wssSendRefreshRequired('devices');
950
972
  }
951
973
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
952
- spawn
953
- .spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
974
+ const { spawnCommand } = await import('./utils/spawn.js');
975
+ spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
954
976
  .then((response) => {
955
977
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
956
978
  this.wssSendCloseSnackbarMessage(`Uninstalling package ${data.params.packageName}...`);
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { promises as fs } from 'node:fs';
4
4
  import EventEmitter from 'node:events';
5
5
  import { inspect } from 'node:util';
6
- import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from 'node-ansi-logger';
6
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
7
7
  import { NodeStorageManager } from 'node-persist-manager';
8
8
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
9
9
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
@@ -11,7 +11,6 @@ import { AggregatorEndpoint } from '@matter/main/endpoints';
11
11
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
12
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
13
13
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
14
- import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
15
14
  import { dev, plg, typ } from './matterbridgeTypes.js';
16
15
  import { PluginManager } from './pluginManager.js';
17
16
  import { DeviceManager } from './deviceManager.js';
@@ -19,7 +18,6 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
19
18
  import { bridge } from './matterbridgeDeviceTypes.js';
20
19
  import { Frontend } from './frontend.js';
21
20
  import { addVirtualDevices } from './helpers.js';
22
- import spawn from './utils/spawn.js';
23
21
  export class Matterbridge extends EventEmitter {
24
22
  systemInformation = {
25
23
  interfaceName: '',
@@ -385,15 +383,15 @@ export class Matterbridge extends EventEmitter {
385
383
  }
386
384
  Logger.format = MatterLogFormat.ANSI;
387
385
  Logger.setLogger('default', this.createMatterLogger());
388
- this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
386
+ this.matterbridgeInformation.matterLoggerLevel = Logger.level;
389
387
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
390
388
  this.matterbridgeInformation.matterFileLogger = true;
391
389
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
392
- defaultLogLevel: Logger.defaultLogLevel,
390
+ defaultLogLevel: Logger.level,
393
391
  logFormat: MatterLogFormat.PLAIN,
394
392
  });
395
393
  }
396
- this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
394
+ this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
397
395
  const networkInterfaces = os.networkInterfaces();
398
396
  const availableAddresses = Object.entries(networkInterfaces);
399
397
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -494,7 +492,8 @@ export class Matterbridge extends EventEmitter {
494
492
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
495
493
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
496
494
  try {
497
- await spawn.spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
495
+ const { spawnCommand } = await import('./utils/spawn.js');
496
+ await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
498
497
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
499
498
  plugin.error = false;
500
499
  }
@@ -605,6 +604,7 @@ export class Matterbridge extends EventEmitter {
605
604
  return;
606
605
  }
607
606
  if (hasParameter('loginterfaces')) {
607
+ const { logInterfaces } = await import('./utils/network.js');
608
608
  this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
609
609
  logInterfaces();
610
610
  this.shutdown = true;
@@ -667,7 +667,7 @@ export class Matterbridge extends EventEmitter {
667
667
  await matterStorageManager.createContext('root')?.clearAll();
668
668
  await matterStorageManager.createContext('sessions')?.clearAll();
669
669
  await matterStorageManager.createContext('persist')?.clearAll();
670
- this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
670
+ this.log.notice(`Reset commissioning for plugin ${plg}${plugin.name}${nt} done! Remove the device from the controller.`);
671
671
  }
672
672
  }
673
673
  else {
@@ -679,10 +679,12 @@ export class Matterbridge extends EventEmitter {
679
679
  }
680
680
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
681
681
  await this.frontend.start(getIntParameter('frontend'));
682
+ clearTimeout(this.checkUpdateTimeout);
682
683
  this.checkUpdateTimeout = setTimeout(async () => {
683
684
  const { checkUpdates } = await import('./update.js');
684
685
  checkUpdates(this);
685
686
  }, 30 * 1000).unref();
687
+ clearInterval(this.checkUpdateInterval);
686
688
  this.checkUpdateInterval = setInterval(async () => {
687
689
  const { checkUpdates } = await import('./update.js');
688
690
  checkUpdates(this);
@@ -853,6 +855,7 @@ export class Matterbridge extends EventEmitter {
853
855
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
854
856
  if (this.globalModulesDirectory === '') {
855
857
  try {
858
+ const { getGlobalNodeModules } = await import('./utils/network.js');
856
859
  this.execRunningCount++;
857
860
  this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
858
861
  this.execRunningCount--;
@@ -905,9 +908,6 @@ export class Matterbridge extends EventEmitter {
905
908
  case MatterLogLevel.FATAL:
906
909
  matterLogger.log("fatal", message);
907
910
  break;
908
- default:
909
- matterLogger.log("debug", message);
910
- break;
911
911
  }
912
912
  };
913
913
  }
@@ -922,11 +922,12 @@ export class Matterbridge extends EventEmitter {
922
922
  }
923
923
  }
924
924
  return async (level, formattedLog) => {
925
- if (fileSize > 100000000)
925
+ if (fileSize > 100000000) {
926
926
  return;
927
+ }
927
928
  fileSize += formattedLog.length;
928
929
  if (fileSize > 100000000) {
929
- await fs.appendFile(filePath, `Logging on file has been stoppped because the file size is greater then 100MB.` + os.EOL);
930
+ await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
930
931
  return;
931
932
  }
932
933
  const now = new Date();
@@ -954,9 +955,6 @@ export class Matterbridge extends EventEmitter {
954
955
  case MatterLogLevel.FATAL:
955
956
  await fs.appendFile(filePath, `[${timestamp}] [${logger}] [fatal] ${finalMessage}`);
956
957
  break;
957
- default:
958
- await fs.appendFile(filePath, `[${timestamp}] [${logger}] ${finalMessage}`);
959
- break;
960
958
  }
961
959
  };
962
960
  }
@@ -969,11 +967,12 @@ export class Matterbridge extends EventEmitter {
969
967
  async updateProcess() {
970
968
  this.log.info('Updating matterbridge...');
971
969
  try {
972
- await spawn.spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
970
+ const { spawnCommand } = await import('./utils/spawn.js');
971
+ await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
973
972
  this.log.info('Matterbridge has been updated. Full restart required.');
974
973
  }
975
974
  catch (error) {
976
- this.log.error('Error updating matterbridge:', error instanceof Error ? error.message : error);
975
+ this.log.error(`Error updating matterbridge: ${error instanceof Error ? error.message : error}`);
977
976
  }
978
977
  this.frontend.wssSendRestartRequired();
979
978
  await this.cleanup('updating...', false);
@@ -1849,8 +1848,8 @@ export class Matterbridge extends EventEmitter {
1849
1848
  };
1850
1849
  });
1851
1850
  }
1852
- sanitizeSessionInformation(session) {
1853
- return session
1851
+ sanitizeSessionInformation(sessions) {
1852
+ return sessions
1854
1853
  .filter((session) => session.isPeerActive)
1855
1854
  .map((session) => {
1856
1855
  return {
@@ -1,68 +1,66 @@
1
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`);
2
+ export async function spawnCommand(matterbridge, command, args) {
3
+ const { spawn } = await import('node:child_process');
4
+ const cmdLine = command + ' ' + args.join(' ');
5
+ if (process.platform === 'win32' && command === 'npm') {
6
+ const argstring = 'npm ' + args.join(' ');
7
+ args.splice(0, args.length, '/c', argstring);
8
+ command = 'cmd.exe';
9
+ }
10
+ if (hasParameter('sudo') || (process.platform !== 'win32' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
11
+ args.unshift(command);
12
+ command = 'sudo';
13
+ }
14
+ matterbridge.log.debug(`Spawn command ${command} with ${args.join(' ')}`);
15
+ return new Promise((resolve, reject) => {
16
+ const childProcess = spawn(command, args, {
17
+ stdio: ['inherit', 'pipe', 'pipe'],
18
+ });
19
+ childProcess.on('error', (err) => {
20
+ matterbridge.log.error(`Failed to start child process "${cmdLine}": ${err.message}`);
21
+ reject(err);
22
+ });
23
+ childProcess.on('close', (code, signal) => {
24
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
25
+ if (code === 0) {
26
+ if (cmdLine.startsWith('npm install -g'))
27
+ matterbridge.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
28
+ matterbridge.log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
50
29
  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
30
  }
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
- });
31
+ else {
32
+ matterbridge.log.error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
33
+ reject(new Error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`));
65
34
  }
66
35
  });
67
- },
68
- };
36
+ childProcess.on('exit', (code, signal) => {
37
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
38
+ if (code === 0) {
39
+ matterbridge.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
40
+ resolve(true);
41
+ }
42
+ else {
43
+ matterbridge.log.error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
44
+ reject(new Error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`));
45
+ }
46
+ });
47
+ childProcess.on('disconnect', () => {
48
+ matterbridge.log.debug(`Child process "${cmdLine}" has been disconnected from the parent`);
49
+ resolve(true);
50
+ });
51
+ if (childProcess.stdout) {
52
+ childProcess.stdout.on('data', (data) => {
53
+ const message = data.toString().trim();
54
+ matterbridge.log.debug(`Spawn output (stdout): ${message}`);
55
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', message);
56
+ });
57
+ }
58
+ if (childProcess.stderr) {
59
+ childProcess.stderr.on('data', (data) => {
60
+ const message = data.toString().trim();
61
+ matterbridge.log.debug(`Spawn verbose (stderr): ${message}`);
62
+ matterbridge.frontend.wssSendMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', message);
63
+ });
64
+ }
65
+ });
66
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "./static/css/main.944b63c3.css",
4
- "main.js": "./static/js/main.42525fab.js",
4
+ "main.js": "./static/js/main.1d25e0d8.js",
5
5
  "static/js/453.d855a71b.chunk.js": "./static/js/453.d855a71b.chunk.js",
6
6
  "static/media/roboto-latin-700-normal.woff2": "./static/media/roboto-latin-700-normal.c4d6cab43bec89049809.woff2",
7
7
  "static/media/roboto-latin-500-normal.woff2": "./static/media/roboto-latin-500-normal.599f66a60bdf974e578e.woff2",
@@ -77,11 +77,11 @@
77
77
  "static/media/roboto-greek-ext-300-normal.woff": "./static/media/roboto-greek-ext-300-normal.60729cafbded24073dfb.woff",
78
78
  "index.html": "./index.html",
79
79
  "main.944b63c3.css.map": "./static/css/main.944b63c3.css.map",
80
- "main.42525fab.js.map": "./static/js/main.42525fab.js.map",
80
+ "main.1d25e0d8.js.map": "./static/js/main.1d25e0d8.js.map",
81
81
  "453.d855a71b.chunk.js.map": "./static/js/453.d855a71b.chunk.js.map"
82
82
  },
83
83
  "entrypoints": [
84
84
  "static/css/main.944b63c3.css",
85
- "static/js/main.42525fab.js"
85
+ "static/js/main.1d25e0d8.js"
86
86
  ]
87
87
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.42525fab.js"></script><link href="./static/css/main.944b63c3.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.1d25e0d8.js"></script><link href="./static/css/main.944b63c3.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>