matterbridge 3.2.9-dev-20250924-9417698 → 3.2.9-dev-20250924-c639a33

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
@@ -21,6 +21,10 @@ If you like this project and find it useful, please consider giving it a star on
21
21
  - [frontend]: Use MbfTable for Plugins, Devices, Registered devices and Clusters tables.
22
22
  - [frontend]: Optimized WebSocker message handlers. Now, the handler targets the component.
23
23
  - [frontend]: Removed dangerouslySetInnerHTML from log rendering.
24
+ - [frontend]: Added push update to Icon view and table view cluster panel.
25
+ - [frontend]: Added install progress dialog when installing or uploading packages.
26
+ - [endpoint]: Added occupancy feature to all Thermostat cluster helpers. When provided (either false or true) it will create a Thermostat with occupancy feature.
27
+ - [endpoint]: Added outdoorTemperature to all Thermostat cluster helpers. Default is undefined (it will be ignored).
24
28
 
25
29
  ### Changed
26
30
 
package/dist/frontend.js CHANGED
@@ -525,7 +525,7 @@ export class Frontend extends EventEmitter {
525
525
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
526
526
  if (filename.endsWith('.tgz')) {
527
527
  const { spawnCommand } = await import('./utils/spawn.js');
528
- await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
528
+ await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
529
529
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
530
530
  this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
531
531
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
@@ -856,7 +856,7 @@ export class Frontend extends EventEmitter {
856
856
  }
857
857
  getClusters(pluginName, endpointNumber) {
858
858
  const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
859
- if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
859
+ if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
860
860
  this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
861
861
  return;
862
862
  }
@@ -872,6 +872,7 @@ export class Frontend extends EventEmitter {
872
872
  return;
873
873
  clusters.push({
874
874
  endpoint: endpoint.number.toString(),
875
+ number: endpoint.number,
875
876
  id: 'main',
876
877
  deviceTypes,
877
878
  clusterName: capitalizeFirstLetter(clusterName),
@@ -899,7 +900,8 @@ export class Frontend extends EventEmitter {
899
900
  return;
900
901
  clusters.push({
901
902
  endpoint: childEndpoint.number.toString(),
902
- id: childEndpoint.maybeId ?? 'null',
903
+ number: childEndpoint.number,
904
+ id: childEndpoint.id,
903
905
  deviceTypes,
904
906
  clusterName: capitalizeFirstLetter(clusterName),
905
907
  clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
@@ -910,7 +912,7 @@ export class Frontend extends EventEmitter {
910
912
  });
911
913
  });
912
914
  });
913
- return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
915
+ return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
914
916
  }
915
917
  async wsMessageHandler(client, message) {
916
918
  let data;
@@ -967,7 +969,7 @@ export class Frontend extends EventEmitter {
967
969
  }
968
970
  this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
969
971
  const { spawnCommand } = await import('./utils/spawn.js');
970
- spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
972
+ spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'], data.params.packageName)
971
973
  .then((_response) => {
972
974
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
973
975
  this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
@@ -1035,7 +1037,7 @@ export class Frontend extends EventEmitter {
1035
1037
  }
1036
1038
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1037
1039
  const { spawnCommand } = await import('./utils/spawn.js');
1038
- spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
1040
+ spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
1039
1041
  .then((_response) => {
1040
1042
  sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
1041
1043
  this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
@@ -1765,9 +1767,9 @@ export class Frontend extends EventEmitter {
1765
1767
  this.log.debug('Sending a close snackbar message to all connected clients');
1766
1768
  this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
1767
1769
  }
1768
- wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
1770
+ wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
1769
1771
  this.log.debug('Sending an attribute update message to all connected clients');
1770
- this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, cluster, attribute, value } });
1772
+ this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
1771
1773
  }
1772
1774
  wssBroadcastMessage(msg) {
1773
1775
  const stringifiedMsg = JSON.stringify(msg);
@@ -3,13 +3,12 @@ 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, nt, BLUE } from 'node-ansi-logger';
6
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE, or } 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 { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
10
10
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
11
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
- import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
13
12
  import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
14
13
  import { withTimeout, waiter, wait } from './utils/wait.js';
15
14
  import { dev, plg, typ } from './matterbridgeTypes.js';
@@ -508,7 +507,7 @@ export class Matterbridge extends EventEmitter {
508
507
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
509
508
  try {
510
509
  const { spawnCommand } = await import('./utils/spawn.js');
511
- await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
510
+ await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], plugin.name);
512
511
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
513
512
  plugin.error = false;
514
513
  }
@@ -950,7 +949,7 @@ export class Matterbridge extends EventEmitter {
950
949
  this.log.info('Updating matterbridge...');
951
950
  try {
952
951
  const { spawnCommand } = await import('./utils/spawn.js');
953
- await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
952
+ await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'matterbridge');
954
953
  this.log.info('Matterbridge has been updated. Full restart required.');
955
954
  }
956
955
  catch (error) {
@@ -1714,20 +1713,65 @@ export class Matterbridge extends EventEmitter {
1714
1713
  await wait(2000);
1715
1714
  }
1716
1715
  async subscribeAttributeChanged(plugin, device) {
1717
- if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId)
1716
+ if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1718
1717
  return;
1719
1718
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1720
1719
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1721
1720
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1722
1721
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
1723
- this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
1722
+ this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1724
1723
  });
1725
1724
  }
1726
- if (device.hasClusterServer(BridgedDeviceBasicInformationServer)) {
1727
- device.eventsOf(BridgedDeviceBasicInformationServer).reachable$Changed.on((reachable) => {
1728
- this.log.info(`Bridged endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
1729
- this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BridgedDeviceBasicInformationServer', 'reachable', reachable);
1730
- });
1725
+ const subscriptions = [
1726
+ { cluster: 'BridgedDeviceBasicInformation', attribute: 'reachable' },
1727
+ { cluster: 'OnOff', attribute: 'onOff' },
1728
+ { cluster: 'LevelControl', attribute: 'currentLevel' },
1729
+ { cluster: 'ColorControl', attribute: 'colorMode' },
1730
+ { cluster: 'ColorControl', attribute: 'currentHue' },
1731
+ { cluster: 'ColorControl', attribute: 'currentSaturation' },
1732
+ { cluster: 'ColorControl', attribute: 'currentX' },
1733
+ { cluster: 'ColorControl', attribute: 'currentY' },
1734
+ { cluster: 'ColorControl', attribute: 'colorTemperatureMireds' },
1735
+ { cluster: 'Thermostat', attribute: 'localTemperature' },
1736
+ { cluster: 'Thermostat', attribute: 'occupiedCoolingSetpoint' },
1737
+ { cluster: 'Thermostat', attribute: 'occupiedHeatingSetpoint' },
1738
+ { cluster: 'Thermostat', attribute: 'systemMode' },
1739
+ { cluster: 'WindowCovering', attribute: 'operationalStatus' },
1740
+ { cluster: 'WindowCovering', attribute: 'currentPositionLiftPercent100ths' },
1741
+ { cluster: 'DoorLock', attribute: 'lockState' },
1742
+ { cluster: 'PumpConfigurationAndControl', attribute: 'pumpStatus' },
1743
+ { cluster: 'FanControl', attribute: 'fanMode' },
1744
+ { cluster: 'FanControl', attribute: 'fanModeSequence' },
1745
+ { cluster: 'FanControl', attribute: 'percentSetting' },
1746
+ { cluster: 'AirQuality', attribute: 'airQuality' },
1747
+ { cluster: 'TotalVolatileOrganicCompoundsConcentrationMeasurement', attribute: 'measuredValue' },
1748
+ { cluster: 'BooleanState', attribute: 'stateValue' },
1749
+ { cluster: 'OccupancySensing', attribute: 'occupancy' },
1750
+ { cluster: 'IlluminanceMeasurement', attribute: 'measuredValue' },
1751
+ { cluster: 'TemperatureMeasurement', attribute: 'measuredValue' },
1752
+ { cluster: 'RelativeHumidityMeasurement', attribute: 'measuredValue' },
1753
+ { cluster: 'PressureMeasurement', attribute: 'measuredValue' },
1754
+ { cluster: 'FlowMeasurement', attribute: 'measuredValue' },
1755
+ ];
1756
+ for (const sub of subscriptions) {
1757
+ if (device.hasAttributeServer(sub.cluster, sub.attribute)) {
1758
+ this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1759
+ await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1760
+ this.log.debug(`Bridged endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
1761
+ this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1762
+ });
1763
+ }
1764
+ for (const child of device.getChildEndpoints()) {
1765
+ for (const sub of subscriptions) {
1766
+ if (child.hasAttributeServer(sub.cluster, sub.attribute)) {
1767
+ this.log.debug(`Subscribing to child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1768
+ await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1769
+ this.log.debug(`Bridged child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
1770
+ this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1771
+ });
1772
+ }
1773
+ }
1774
+ }
1731
1775
  }
1732
1776
  }
1733
1777
  sanitizeFabricInformations(fabricInfo) {
@@ -778,9 +778,10 @@ export class MatterbridgeEndpoint extends Endpoint {
778
778
  this.log.debug(`Set WindowCovering currentPositionTiltPercent100ths: ${tiltPosition} and targetPositionTiltPercent100ths: ${tiltPosition}.`);
779
779
  }
780
780
  }
781
- createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
782
- this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode), {
781
+ createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedHeatingSetpoint = undefined, unoccupiedCoolingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined) {
782
+ this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
783
783
  localTemperature: localTemperature * 100,
784
+ ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
784
785
  systemMode: Thermostat.SystemMode.Auto,
785
786
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
786
787
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
@@ -795,12 +796,16 @@ export class MatterbridgeEndpoint extends Endpoint {
795
796
  absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
796
797
  minSetpointDeadBand: minSetpointDeadBand * 100,
797
798
  thermostatRunningMode: Thermostat.ThermostatRunningMode.Off,
799
+ ...(occupied !== undefined ? { unoccupiedHeatingSetpoint: unoccupiedHeatingSetpoint !== undefined ? unoccupiedHeatingSetpoint * 100 : 1900 } : {}),
800
+ ...(occupied !== undefined ? { unoccupiedCoolingSetpoint: unoccupiedCoolingSetpoint !== undefined ? unoccupiedCoolingSetpoint * 100 : 2700 } : {}),
801
+ ...(occupied !== undefined ? { occupancy: { occupied } } : {}),
798
802
  });
799
803
  return this;
800
804
  }
801
- createDefaultHeatingThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50) {
802
- this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating), {
805
+ createDefaultHeatingThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, unoccupiedHeatingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined) {
806
+ this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
803
807
  localTemperature: localTemperature * 100,
808
+ ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
804
809
  systemMode: Thermostat.SystemMode.Heat,
805
810
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.HeatingOnly,
806
811
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
@@ -808,12 +813,15 @@ export class MatterbridgeEndpoint extends Endpoint {
808
813
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
809
814
  absMinHeatSetpointLimit: minHeatSetpointLimit * 100,
810
815
  absMaxHeatSetpointLimit: maxHeatSetpointLimit * 100,
816
+ ...(occupied !== undefined ? { unoccupiedHeatingSetpoint: unoccupiedHeatingSetpoint !== undefined ? unoccupiedHeatingSetpoint * 100 : 1900 } : {}),
817
+ ...(occupied !== undefined ? { occupancy: { occupied } } : {}),
811
818
  });
812
819
  return this;
813
820
  }
814
- createDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
815
- this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Cooling), {
821
+ createDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedCoolingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined) {
822
+ this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Cooling, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
816
823
  localTemperature: localTemperature * 100,
824
+ ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
817
825
  systemMode: Thermostat.SystemMode.Cool,
818
826
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingOnly,
819
827
  occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
@@ -821,6 +829,8 @@ export class MatterbridgeEndpoint extends Endpoint {
821
829
  maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
822
830
  absMinCoolSetpointLimit: minCoolSetpointLimit * 100,
823
831
  absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
832
+ ...(occupied !== undefined ? { unoccupiedCoolingSetpoint: unoccupiedCoolingSetpoint !== undefined ? unoccupiedCoolingSetpoint * 100 : 2700 } : {}),
833
+ ...(occupied !== undefined ? { occupancy: { occupied } } : {}),
824
834
  });
825
835
  return this;
826
836
  }
@@ -1,5 +1,5 @@
1
1
  import { hasParameter } from './commandLine.js';
2
- export async function spawnCommand(matterbridge, command, args) {
2
+ export async function spawnCommand(matterbridge, command, args, packageName) {
3
3
  const { spawn } = await import('node:child_process');
4
4
  const cmdLine = command + ' ' + args.join(' ');
5
5
  if (process.platform === 'win32' && command === 'npm') {
@@ -13,6 +13,7 @@ export async function spawnCommand(matterbridge, command, args) {
13
13
  }
14
14
  matterbridge.log.debug(`Spawn command ${command} with ${args.join(' ')}`);
15
15
  return new Promise((resolve, reject) => {
16
+ matterbridge.frontend.wssSendLogMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn-init', packageName || `unknown-package`);
16
17
  const childProcess = spawn(command, args, {
17
18
  stdio: ['inherit', 'pipe', 'pipe'],
18
19
  });
@@ -22,6 +23,7 @@ export async function spawnCommand(matterbridge, command, args) {
22
23
  });
23
24
  childProcess.on('close', (code, signal) => {
24
25
  matterbridge.frontend.wssSendLogMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
26
+ matterbridge.frontend.wssSendLogMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn-exit', 'Child process closed');
25
27
  if (code === 0) {
26
28
  if (cmdLine.startsWith('npm install -g'))
27
29
  matterbridge.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
@@ -35,6 +37,7 @@ export async function spawnCommand(matterbridge, command, args) {
35
37
  });
36
38
  childProcess.on('exit', (code, signal) => {
37
39
  matterbridge.frontend.wssSendLogMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
40
+ matterbridge.frontend.wssSendLogMessage('spawn', matterbridge.log.now(), 'Matterbridge:spawn-exit', 'Child process exited');
38
41
  if (code === 0) {
39
42
  matterbridge.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
40
43
  resolve(true);