matterbridge 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +615 -551
  2. package/README.md +643 -594
  3. package/dist/cluster/ElectricalEnergyMeasurementCluster.js +1 -1
  4. package/dist/cluster/ElectricalEnergyMeasurementCluster.js.map +1 -1
  5. package/dist/cluster/ElectricalPowerMeasurementCluster.js +10 -10
  6. package/dist/cluster/ElectricalPowerMeasurementCluster.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +0 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/logger/export.d.ts +2 -0
  11. package/dist/logger/export.d.ts.map +1 -0
  12. package/dist/logger/export.js +2 -0
  13. package/dist/logger/export.js.map +1 -0
  14. package/dist/matterbridge.d.ts +7 -0
  15. package/dist/matterbridge.d.ts.map +1 -1
  16. package/dist/matterbridge.js +248 -320
  17. package/dist/matterbridge.js.map +1 -1
  18. package/dist/matterbridgeController.d.ts.map +1 -1
  19. package/dist/matterbridgeController.js +0 -3
  20. package/dist/matterbridgeController.js.map +1 -1
  21. package/dist/matterbridgeDevice.d.ts +213 -4
  22. package/dist/matterbridgeDevice.d.ts.map +1 -1
  23. package/dist/matterbridgeDevice.js +133 -46
  24. package/dist/matterbridgeDevice.js.map +1 -1
  25. package/dist/storage/export.d.ts +2 -0
  26. package/dist/storage/export.d.ts.map +1 -0
  27. package/dist/storage/export.js +2 -0
  28. package/dist/storage/export.js.map +1 -0
  29. package/dist/utils/colorUtils.d.ts +61 -0
  30. package/dist/utils/colorUtils.d.ts.map +1 -0
  31. package/dist/utils/colorUtils.js +313 -0
  32. package/dist/utils/colorUtils.js.map +1 -0
  33. package/dist/utils/utils.d.ts +73 -0
  34. package/dist/utils/utils.d.ts.map +1 -0
  35. package/dist/utils/utils.js +266 -0
  36. package/dist/utils/utils.js.map +1 -0
  37. package/dist/utils.d.ts +1 -0
  38. package/dist/utils.d.ts.map +1 -1
  39. package/dist/utils.js +4 -0
  40. package/dist/utils.js.map +1 -1
  41. package/frontend/build/asset-manifest.json +3 -3
  42. package/frontend/build/index.html +1 -1
  43. package/frontend/build/static/js/{main.cbfc6c9b.js → main.c3b5dfce.js} +3 -3
  44. package/frontend/build/static/js/{main.cbfc6c9b.js.map → main.c3b5dfce.js.map} +1 -1
  45. package/package.json +57 -21
  46. /package/frontend/build/static/js/{main.cbfc6c9b.js.LICENSE.txt → main.c3b5dfce.js.LICENSE.txt} +0 -0
@@ -25,6 +25,7 @@ import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugSt
25
25
  import { fileURLToPath, pathToFileURL } from 'url';
26
26
  import { promises as fs } from 'fs';
27
27
  import { exec, spawn } from 'child_process';
28
+ import { createServer } from 'http';
28
29
  import https from 'https';
29
30
  import EventEmitter from 'events';
30
31
  import express from 'express';
@@ -35,6 +36,7 @@ import WebSocket, { WebSocketServer } from 'ws';
35
36
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
36
37
  import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js';
37
38
  import { BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster } from './cluster/BridgedDeviceBasicInformationCluster.js';
39
+ import { logInterfaces } from './utils/utils.js';
38
40
  // @project-chip/matter-node.js
39
41
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
40
42
  import { BasicInformationCluster, ClusterServer, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, ThreadNetworkDiagnosticsCluster, getClusterNameById } from '@project-chip/matter-node.js/cluster';
@@ -54,6 +56,7 @@ const typ = '\u001B[38;5;207m';
54
56
  */
55
57
  export class Matterbridge extends EventEmitter {
56
58
  systemInformation = {
59
+ interfaceName: '',
57
60
  macAddress: '',
58
61
  ipv4Address: '',
59
62
  ipv6Address: '',
@@ -100,12 +103,16 @@ export class Matterbridge extends EventEmitter {
100
103
  port = 5540;
101
104
  log;
102
105
  hasCleanupStarted = false;
106
+ plugins = new Map();
107
+ devices = new Map();
103
108
  registeredPlugins = [];
104
109
  registeredDevices = [];
105
110
  nodeStorage;
106
111
  nodeContext;
107
112
  expressApp;
108
113
  expressServer;
114
+ httpServer;
115
+ httpsServer;
109
116
  webSocketServer;
110
117
  storageManager;
111
118
  matterbridgeContext;
@@ -256,10 +263,14 @@ export class Matterbridge extends EventEmitter {
256
263
  - bridge: start Matterbridge in bridge mode
257
264
  - childbridge: start Matterbridge in childbridge mode
258
265
  - frontend [port]: start the frontend on the given port (default 8283)
259
- - debug: enable debug mode (default false)
266
+ - debug: enable the Matterbridge debug mode (default false)
267
+ - matterlogger: set the matter.js logger level: debug | info | notice | warn | error | fatal (default info)
260
268
  - reset: remove the commissioning for Matterbridge (bridge mode). Shutdown Matterbridge before using it!
261
269
  - factoryreset: remove all commissioning information and reset all internal storages. Shutdown Matterbridge before using it!
262
270
  - list: list the registered plugins
271
+ - loginterfaces: log the network interfaces
272
+ - logstorage: log the node storage
273
+ - ssl: enable SSL for the frontend and WebSockerServer (certificates in .matterbridge/certs directory cert.pem, key.pem and ca.pem (optional))
263
274
  - add [plugin path]: register the plugin from the given absolute or relative path
264
275
  - add [plugin name]: register the globally installed plugin with the given name
265
276
  - remove [plugin path]: remove the plugin from the given absolute or relative path
@@ -305,9 +316,7 @@ export class Matterbridge extends EventEmitter {
305
316
  }
306
317
  // Log system info and create .matterbridge directory
307
318
  await this.logNodeAndSystemInfo();
308
- this.log.info(
309
- // eslint-disable-next-line max-len
310
- `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} ` +
319
+ this.log.info(`Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} ` +
311
320
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
312
321
  // Check node version and throw error
313
322
  requireMinNodeVersion(18);
@@ -380,7 +389,7 @@ export class Matterbridge extends EventEmitter {
380
389
  process.exit(0);
381
390
  }
382
391
  if (hasParameter('logstorage')) {
383
- this.log.info(`${plg}matterbridge${nf} storage log`);
392
+ this.log.info(`${plg}Matterbridge${nf} storage log`);
384
393
  await this.nodeContext?.logStorage();
385
394
  for (const plugin of this.registeredPlugins) {
386
395
  this.log.info(`${plg}${plugin.name}${nf} storage log`);
@@ -389,6 +398,12 @@ export class Matterbridge extends EventEmitter {
389
398
  this.emit('shutdown');
390
399
  process.exit(0);
391
400
  }
401
+ if (hasParameter('loginterfaces')) {
402
+ this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
403
+ logInterfaces();
404
+ this.emit('shutdown');
405
+ process.exit(0);
406
+ }
392
407
  if (getParameter('add')) {
393
408
  this.log.debug(`Registering plugin ${getParameter('add')}`);
394
409
  await this.executeCommandLine(getParameter('add'), 'add');
@@ -507,10 +522,13 @@ export class Matterbridge extends EventEmitter {
507
522
  continue;
508
523
  }
509
524
  plugin.error = false;
525
+ plugin.locked = false;
510
526
  plugin.loaded = false;
511
527
  plugin.started = false;
512
528
  plugin.configured = false;
513
529
  plugin.connected = undefined;
530
+ plugin.registeredDevices = undefined;
531
+ plugin.addedDevices = undefined;
514
532
  plugin.qrPairingCode = undefined;
515
533
  plugin.manualPairingCode = undefined;
516
534
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
@@ -537,10 +555,13 @@ export class Matterbridge extends EventEmitter {
537
555
  continue;
538
556
  }
539
557
  plugin.error = false;
558
+ plugin.locked = false;
540
559
  plugin.loaded = false;
541
560
  plugin.started = false;
542
561
  plugin.configured = false;
543
562
  plugin.connected = false;
563
+ plugin.registeredDevices = undefined;
564
+ plugin.addedDevices = undefined;
544
565
  plugin.qrPairingCode = (await plugin.nodeContext?.get('qrPairingCode', undefined)) ?? undefined;
545
566
  plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
546
567
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
@@ -549,6 +570,28 @@ export class Matterbridge extends EventEmitter {
549
570
  return;
550
571
  }
551
572
  }
573
+ async savePluginsToStorage() {
574
+ if (!this.nodeContext) {
575
+ this.log.error('loadPluginsFromStorage() error: the node context is not initialized');
576
+ return;
577
+ }
578
+ // Convert the map to an array
579
+ // const pluginArray = Array.from(this.plugins.values());
580
+ // await this.nodeContext.set('plugins', pluginArray);
581
+ // TODO remove after migration done
582
+ await this.nodeContext.set('plugins', await this.getBaseRegisteredPlugins());
583
+ }
584
+ async loadPluginsFromStorage() {
585
+ if (!this.nodeContext) {
586
+ this.log.error('loadPluginsFromStorage() error: the node context is not initialized');
587
+ return;
588
+ }
589
+ // Load the array from storage and convert it back to a map
590
+ // const pluginArray = await this.nodeContext.get<RegisteredPlugin[]>('plugins', []);
591
+ // for (const plugin of pluginArray) this.plugins.set(plugin.name, plugin);
592
+ // TODO remove after migration done
593
+ this.registeredPlugins = await this.nodeContext.get('plugins', []);
594
+ }
552
595
  /**
553
596
  * Resolves the name of a plugin by loading and parsing its package.json file.
554
597
  * @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
@@ -573,7 +616,6 @@ export class Matterbridge extends EventEmitter {
573
616
  this.log.debug(`Package.json not found at ${packageJsonPath}`);
574
617
  this.log.debug(`Trying at ${this.globalModulesDirectory}`);
575
618
  packageJsonPath = path.join(this.globalModulesDirectory, pluginPath);
576
- // this.log.debug(`Got ${packageJsonPath}`);
577
619
  }
578
620
  try {
579
621
  // Load the package.json of the plugin
@@ -758,7 +800,7 @@ export class Matterbridge extends EventEmitter {
758
800
  this.log.debug('All listeners removed');
759
801
  this.checkUpdateInterval && clearInterval(this.checkUpdateInterval);
760
802
  this.checkUpdateInterval = undefined;
761
- // Calling the shutdown functions with a reason
803
+ // Calling the shutdown method of each plugin
762
804
  for (const plugin of this.registeredPlugins) {
763
805
  if (!plugin.enabled || plugin.error)
764
806
  continue;
@@ -817,14 +859,29 @@ export class Matterbridge extends EventEmitter {
817
859
  // Close the express server
818
860
  if (this.expressServer) {
819
861
  this.expressServer.close();
862
+ this.expressServer.removeAllListeners();
820
863
  this.expressServer = undefined;
821
864
  this.log.debug('Express server closed successfully');
822
865
  }
823
- // Remove listeners
866
+ // Close the http server
867
+ if (this.httpServer) {
868
+ this.httpServer.close();
869
+ this.httpServer.removeAllListeners();
870
+ this.httpServer = undefined;
871
+ this.log.debug('Frontend http server closed successfully');
872
+ }
873
+ // Close the https server
874
+ if (this.httpsServer) {
875
+ this.httpsServer.close();
876
+ this.httpsServer.removeAllListeners();
877
+ this.httpsServer = undefined;
878
+ this.log.debug('Frontend https server closed successfully');
879
+ }
880
+ // Remove listeners from the express app
824
881
  if (this.expressApp) {
825
882
  this.expressApp.removeAllListeners();
826
883
  this.expressApp = undefined;
827
- this.log.debug('Frontend closed successfully');
884
+ this.log.debug('Frontend app closed successfully');
828
885
  }
829
886
  // Close the WebSocket server
830
887
  if (this.webSocketServer) {
@@ -1236,8 +1293,8 @@ export class Matterbridge extends EventEmitter {
1236
1293
  await fs.access(configFile);
1237
1294
  const data = await fs.readFile(configFile, 'utf8');
1238
1295
  const config = JSON.parse(data);
1239
- // this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
1240
1296
  this.log.debug(`Config file found: ${configFile}.`);
1297
+ // this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
1241
1298
  /* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/
1242
1299
  config.name = plugin.name;
1243
1300
  config.type = plugin.type;
@@ -1547,7 +1604,6 @@ export class Matterbridge extends EventEmitter {
1547
1604
  this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1548
1605
  for (const nodeId of nodeIds) {
1549
1606
  this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1550
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1551
1607
  const node = await this.commissioningController.connectNode(nodeId, {
1552
1608
  attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) => this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1553
1609
  eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) => this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
@@ -1590,9 +1646,7 @@ export class Matterbridge extends EventEmitter {
1590
1646
  });
1591
1647
  this.log.warn(`Cluster: ${cluster.name} attributes:`);
1592
1648
  attributes.forEach((attribute) => {
1593
- this.log.info(
1594
- // eslint-disable-next-line max-len
1595
- `- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1649
+ this.log.info(`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1596
1650
  });
1597
1651
  // Log PowerSourceCluster
1598
1652
  cluster = PowerSourceCluster;
@@ -1601,9 +1655,7 @@ export class Matterbridge extends EventEmitter {
1601
1655
  });
1602
1656
  this.log.warn(`Cluster: ${cluster.name} attributes:`);
1603
1657
  attributes.forEach((attribute) => {
1604
- this.log.info(
1605
- // eslint-disable-next-line max-len
1606
- `- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1658
+ this.log.info(`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1607
1659
  });
1608
1660
  // Log ThreadNetworkDiagnostics
1609
1661
  cluster = ThreadNetworkDiagnosticsCluster;
@@ -1612,9 +1664,7 @@ export class Matterbridge extends EventEmitter {
1612
1664
  });
1613
1665
  this.log.warn(`Cluster: ${cluster.name} attributes:`);
1614
1666
  attributes.forEach((attribute) => {
1615
- this.log.info(
1616
- // eslint-disable-next-line max-len
1617
- `- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1667
+ this.log.info(`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1618
1668
  });
1619
1669
  }
1620
1670
  }
@@ -1635,7 +1685,6 @@ export class Matterbridge extends EventEmitter {
1635
1685
  let failCount = 0;
1636
1686
  const startMatterInterval = setInterval(async () => {
1637
1687
  for (const plugin of this.registeredPlugins) {
1638
- // if (!plugin.enabled || plugin.error) continue;
1639
1688
  // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1640
1689
  if (!plugin.enabled)
1641
1690
  continue;
@@ -1661,53 +1710,20 @@ export class Matterbridge extends EventEmitter {
1661
1710
  this.log.debug('***Cleared startMatterInterval interval for Matterbridge');
1662
1711
  await this.startMatterServer();
1663
1712
  this.log.info('Matter server started');
1664
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1713
+ // Configure the plugins
1665
1714
  /*
1666
- const gdcCluster = this.commissioningServer?.getRootClusterServer(GeneralDiagnosticsCluster);
1667
- if (gdcCluster) {
1668
- console.log('GeneralDiagnosticsCluster', gdcCluster);
1669
- const net = gdcCluster.getNetworkInterfacesAttribute();
1670
- console.log('NetworkInterfaces', net);
1671
-
1672
- // We have like "30:f6:ef:69:2b:c5" in this.systemInformation.macAddress
1673
- const macArray = this.systemInformation.macAddress.split(':').map((hex) => parseInt(hex, 16));
1674
- let hardwareAddress = new Uint8Array(macArray);
1675
- if (hardwareAddress.length === 6) hardwareAddress = Uint8Array.from([0, 0, ...hardwareAddress]);
1676
- // We have like "192.168.1.189" in this.systemInformation.ipv4Address
1677
- const ipv4Array = this.systemInformation.ipv4Address.split('.').map((num) => parseInt(num));
1678
- const iPv4Address = new Uint8Array(ipv4Array);
1679
- // We have like "fd78:cbf8:4939:746:d555:85a9:74f6:9c6" in this.systemInformation.ipv6Address
1680
- const ipv6Groups = this.systemInformation.ipv6Address.split(':');
1681
- const ipv6Array = [];
1682
- for (const group of ipv6Groups) {
1683
- const decimal = parseInt(group, 16);
1684
- ipv6Array.push(decimal >> 8); // High byte
1685
- ipv6Array.push(decimal & 0xff); // Low byte
1686
- }
1687
- const iPv6Address = new Uint8Array(ipv6Array);
1688
- this.log.warn(`GeneralDiagnosticsCluster for hardwareAddress ${this.systemInformation.macAddress} => ${debugStringify(hardwareAddress)}`);
1689
- this.log.warn(`GeneralDiagnosticsCluster for iPv4Address ${this.systemInformation.ipv4Address} => ${debugStringify(iPv4Address)}`);
1690
- this.log.warn(`GeneralDiagnosticsCluster for iPv6Address ${this.systemInformation.ipv6Address} => ${debugStringify(iPv6Address)}`);
1715
+ for (const plugin of this.registeredPlugins) {
1716
+ if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error) continue;
1691
1717
  try {
1692
- gdcCluster.setNetworkInterfacesAttribute([
1693
- {
1694
- name: 'eth0',
1695
- isOperational: true,
1696
- offPremiseServicesReachableIPv4: null,
1697
- offPremiseServicesReachableIPv6: null,
1698
- hardwareAddress,
1699
- iPv4Addresses: [iPv4Address],
1700
- iPv6Addresses: [iPv6Address],
1701
- type: GeneralDiagnostics.InterfaceType.Ethernet,
1702
- },
1703
- ]);
1704
- const net = gdcCluster.getNetworkInterfacesAttribute();
1705
- console.log('NetworkInterfaces', net);
1718
+ this.configurePlugin(plugin); // No await do it asyncronously
1706
1719
  } catch (error) {
1707
- this.log.error('GeneralDiagnosticsCluster.setNetworkInterfacesAttribute error:', error);
1720
+ plugin.error = true;
1721
+ this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1708
1722
  }
1709
1723
  }
1710
1724
  */
1725
+ // Show the QR code for commissioning or log the already commissioned message
1726
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1711
1727
  // Setting reachability to true
1712
1728
  setTimeout(() => {
1713
1729
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
@@ -1723,14 +1739,11 @@ export class Matterbridge extends EventEmitter {
1723
1739
  // addDevice and addBridgedDeevice create the commissionig servers and add the devices to the the commissioning server or to the aggregator
1724
1740
  // Plugins are configured by callback when the plugin is commissioned
1725
1741
  // Start the interval to check if all plugins are loaded and started and so start the matter server
1726
- // TODO set a counter or a timeout
1727
1742
  this.log.debug('***Starting start matter interval in childbridge mode...');
1728
1743
  let failCount = 0;
1729
1744
  const startMatterInterval = setInterval(async () => {
1730
1745
  let allStarted = true;
1731
- // this.registeredPlugins.forEach((plugin) => {
1732
1746
  for (const plugin of this.registeredPlugins) {
1733
- // if (!plugin.enabled || plugin.error) return;
1734
1747
  // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1735
1748
  if (!plugin.enabled)
1736
1749
  continue;
@@ -2093,9 +2106,7 @@ export class Matterbridge extends EventEmitter {
2093
2106
  const info = commissioningServer.getActiveSessionInformation(fabricIndex);
2094
2107
  let connected = false;
2095
2108
  info.forEach((session) => {
2096
- this.log.info(
2097
- // eslint-disable-next-line max-len
2098
- `*Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
2109
+ this.log.info(`*Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
2099
2110
  if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
2100
2111
  this.log.info(`*Controller ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nf} on session ${session.name}`);
2101
2112
  connected = true;
@@ -2172,48 +2183,6 @@ export class Matterbridge extends EventEmitter {
2172
2183
  }
2173
2184
  },
2174
2185
  });
2175
- /*
2176
- const gdcCluster = commissioningServer.getRootClusterServer(GeneralDiagnosticsCluster);
2177
- if (gdcCluster) {
2178
- // console.log('GeneralDiagnosticsCluster found for', plg, pluginName, db);
2179
- // console.log('GeneralDiagnosticsCluster', gdcCluster);
2180
- // We have like "30:f6:ef:69:2b:c5" in this.systemInformation.macAddress
2181
- const macArray = this.systemInformation.macAddress.split(':').map((hex) => parseInt(hex, 16));
2182
- let hardwareAddress = new Uint8Array(macArray);
2183
- if (hardwareAddress.length === 6) hardwareAddress = Uint8Array.from([0, 0, ...hardwareAddress]);
2184
- // We have like "192.168.1.189" in this.systemInformation.ipv4Address
2185
- const ipv4Array = this.systemInformation.ipv4Address.split('.').map((num) => parseInt(num));
2186
- const iPv4Address = new Uint8Array(ipv4Array);
2187
- // We have like "fd78:cbf8:4939:746:d555:85a9:74f6:9c6" in this.systemInformation.ipv6Address
2188
- const ipv6Groups = this.systemInformation.ipv6Address.split(':');
2189
- const ipv6Array = [];
2190
- for (const group of ipv6Groups) {
2191
- const decimal = parseInt(group, 16);
2192
- ipv6Array.push(decimal >> 8); // High byte
2193
- ipv6Array.push(decimal & 0xff); // Low byte
2194
- }
2195
- const iPv6Address = new Uint8Array(ipv6Array);
2196
- this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} hardwareAddress ${this.systemInformation.macAddress} => ${debugStringify(hardwareAddress)}`);
2197
- this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} iPv4Address ${this.systemInformation.ipv4Address} => ${debugStringify(iPv4Address)}`);
2198
- this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} iPv6Address ${this.systemInformation.ipv6Address} => ${debugStringify(iPv6Address)}`);
2199
- try {
2200
- gdcCluster.setNetworkInterfacesAttribute([
2201
- {
2202
- name: 'eth0',
2203
- isOperational: true,
2204
- offPremiseServicesReachableIPv4: null,
2205
- offPremiseServicesReachableIPv6: null,
2206
- hardwareAddress,
2207
- iPv4Addresses: [iPv4Address],
2208
- iPv6Addresses: [iPv6Address],
2209
- type: GeneralDiagnostics.InterfaceType.Ethernet,
2210
- },
2211
- ]);
2212
- } catch (error) {
2213
- this.log.error(`GeneralDiagnosticsCluster.setNetworkInterfacesAttribute for ${plg}${pluginName}${er} error:`, error);
2214
- }
2215
- } else this.log.warn(`*GeneralDiagnosticsCluster not found for ${plg}${pluginName}${wr}`);
2216
- */
2217
2186
  commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
2218
2187
  return commissioningServer;
2219
2188
  }
@@ -2321,24 +2290,29 @@ export class Matterbridge extends EventEmitter {
2321
2290
  async logNodeAndSystemInfo() {
2322
2291
  // IP address information
2323
2292
  const networkInterfaces = os.networkInterfaces();
2324
- this.systemInformation.ipv4Address = 'Not found';
2325
- this.systemInformation.ipv6Address = 'Not found';
2326
- for (const interfaceDetails of Object.values(networkInterfaces)) {
2293
+ this.systemInformation.ipv4Address = '';
2294
+ this.systemInformation.ipv6Address = '';
2295
+ for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
2327
2296
  if (!interfaceDetails) {
2328
2297
  break;
2329
2298
  }
2330
2299
  for (const detail of interfaceDetails) {
2331
- if (detail.family === 'IPv4' && !detail.internal && this.systemInformation.ipv4Address === 'Not found') {
2300
+ if (detail.family === 'IPv4' && !detail.internal && this.systemInformation.ipv4Address === '') {
2301
+ this.systemInformation.interfaceName = interfaceName;
2332
2302
  this.systemInformation.ipv4Address = detail.address;
2333
2303
  this.systemInformation.macAddress = detail.mac;
2334
2304
  }
2335
- else if (detail.family === 'IPv6' && !detail.internal && this.systemInformation.ipv6Address === 'Not found') {
2305
+ else if (detail.family === 'IPv6' && !detail.internal && this.systemInformation.ipv6Address === '') {
2306
+ this.systemInformation.interfaceName = interfaceName;
2336
2307
  this.systemInformation.ipv6Address = detail.address;
2337
2308
  this.systemInformation.macAddress = detail.mac;
2338
2309
  }
2339
2310
  }
2340
- // Break if both addresses are found to improve efficiency
2341
- if (this.systemInformation.ipv4Address !== 'Not found' && this.systemInformation.ipv6Address !== 'Not found') {
2311
+ if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
2312
+ this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
2313
+ this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
2314
+ this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
2315
+ this.log.debug(`- with IPv6 address: '${this.systemInformation.ipv6Address}'`);
2342
2316
  break;
2343
2317
  }
2344
2318
  }
@@ -2361,6 +2335,7 @@ export class Matterbridge extends EventEmitter {
2361
2335
  this.log.debug('Host System Information:');
2362
2336
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
2363
2337
  this.log.debug(`- User: ${this.systemInformation.user}`);
2338
+ this.log.debug(`- Interface: ${this.systemInformation.interfaceName}`);
2364
2339
  this.log.debug(`- MAC Address: ${this.systemInformation.macAddress}`);
2365
2340
  this.log.debug(`- IPv4 Address: ${this.systemInformation.ipv4Address}`);
2366
2341
  this.log.debug(`- IPv6 Address: ${this.systemInformation.ipv6Address}`);
@@ -2532,9 +2507,10 @@ export class Matterbridge extends EventEmitter {
2532
2507
  type: plugin.type,
2533
2508
  name: plugin.name,
2534
2509
  version: plugin.version,
2535
- latestVersion: plugin.latestVersion,
2536
2510
  description: plugin.description,
2537
2511
  author: plugin.author,
2512
+ latestVersion: plugin.latestVersion,
2513
+ locked: plugin.locked,
2538
2514
  error: plugin.error,
2539
2515
  enabled: plugin.enabled,
2540
2516
  loaded: plugin.loaded,
@@ -2542,11 +2518,11 @@ export class Matterbridge extends EventEmitter {
2542
2518
  configured: plugin.configured,
2543
2519
  paired: plugin.paired,
2544
2520
  connected: plugin.connected,
2521
+ fabricInfo: plugin.fabricInfo,
2545
2522
  registeredDevices: plugin.registeredDevices,
2523
+ addedDevices: plugin.addedDevices,
2546
2524
  qrPairingCode: plugin.qrPairingCode,
2547
2525
  manualPairingCode: plugin.manualPairingCode,
2548
- // configJson: includeConfigSchema ? await this.loadPluginConfig(plugin) : {},
2549
- // schemaJson: includeConfigSchema ? await this.loadPluginSchema(plugin) : {},
2550
2526
  configJson: includeConfigSchema ? plugin.configJson : {},
2551
2527
  schemaJson: includeConfigSchema ? plugin.schemaJson : {},
2552
2528
  });
@@ -2662,6 +2638,7 @@ export class Matterbridge extends EventEmitter {
2662
2638
  const cleanMessage = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2663
2639
  // Remove leading asterisks from the message
2664
2640
  const finalMessage = cleanMessage.replace(/^\*+/, '');
2641
+ // Send the message to all connected clients
2665
2642
  this.webSocketServer?.clients.forEach((client) => {
2666
2643
  if (client.readyState === WebSocket.OPEN) {
2667
2644
  client.send(JSON.stringify({ type, subType, message: finalMessage }));
@@ -2674,65 +2651,156 @@ export class Matterbridge extends EventEmitter {
2674
2651
  * @param port The port number to run the frontend server on. Default is 3000.
2675
2652
  */
2676
2653
  async initializeFrontend(port = 8283) {
2677
- this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2678
- const wssPort = 8284;
2679
- const useHttps = false;
2680
- // const wssHost = (useHttps ? 'wss://' : 'ws://') + `${os.hostname().toLowerCase()}:${wssPort}`;
2681
- const wssHost = (useHttps ? 'wss://' : 'ws://') + `${this.systemInformation.ipv4Address}:${wssPort}`;
2682
- if (!useHttps) {
2683
- // Create a WebSocket server no certificate required
2684
- this.webSocketServer = new WebSocketServer({ port: wssPort });
2685
- // this.log.info(`WebSocket server listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2654
+ this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2655
+ // Create the express app that serves the frontend
2656
+ this.expressApp = express();
2657
+ this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2658
+ if (!hasParameter('ssl')) {
2659
+ // Create an HTTP server and attach the express app
2660
+ this.httpServer = createServer(this.expressApp);
2661
+ // Listen on the specified port
2662
+ this.httpServer.listen(port, () => {
2663
+ if (this.systemInformation.ipv4Address !== '')
2664
+ this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
2665
+ if (this.systemInformation.ipv6Address !== '')
2666
+ this.log.debug(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2667
+ });
2668
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2669
+ this.httpServer.on('error', (error) => {
2670
+ this.log.error(`Frontend http server error listening on ${port}`);
2671
+ switch (error.code) {
2672
+ case 'EACCES':
2673
+ this.log.error(`Port ${port} requires elevated privileges`);
2674
+ break;
2675
+ case 'EADDRINUSE':
2676
+ this.log.error(`Port ${port} is already in use`);
2677
+ break;
2678
+ }
2679
+ process.exit(1);
2680
+ });
2686
2681
  }
2687
2682
  else {
2688
- // Define the options for HTTPS server
2689
- // openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mykey.key -out mycert.pem -config openssl.cnf
2690
- // For wss connect the browser to https://laptop5_luca:8284/ https://192.168.1.189/log and accept the certificate
2691
- const serverOptions = {
2692
- cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/mycert.pem')), // Ensure the path is correct
2693
- key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/mykey.key')), // Ensure the path is correct
2694
- // cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.pem')), // Ensure the path is correct
2695
- // key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.key')), // Ensure the path is correct
2696
- };
2697
- // Create an HTTPS server
2698
- const httpsServer = https.createServer(serverOptions);
2699
- // Attach WebSocket server to HTTPS server
2700
- this.webSocketServer = new WebSocketServer({ server: httpsServer });
2701
- // Listen on a specific port
2702
- httpsServer.listen(wssPort, () => {
2703
- this.log.info(`WebSocket server listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2683
+ // Load the SSL certificate, the private key and optionally the CA certificate
2684
+ let cert;
2685
+ try {
2686
+ cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
2687
+ this.log.info(`Loaded certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}`);
2688
+ }
2689
+ catch (error) {
2690
+ this.log.error(`Error reading certificate file: ${error}`);
2691
+ process.exit(1);
2692
+ }
2693
+ let key;
2694
+ try {
2695
+ key = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
2696
+ this.log.info(`Loaded key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}`);
2697
+ }
2698
+ catch (error) {
2699
+ this.log.error(`Error reading key file: ${error}`);
2700
+ process.exit(1);
2701
+ }
2702
+ let ca;
2703
+ try {
2704
+ ca = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
2705
+ this.log.info(`Loaded CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')}`);
2706
+ }
2707
+ catch (error) {
2708
+ this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2709
+ }
2710
+ const serverOptions = { cert, key, ca };
2711
+ // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
2712
+ this.httpsServer = https.createServer(serverOptions, this.expressApp);
2713
+ // Listen on the specified port
2714
+ this.httpsServer.listen(port, () => {
2715
+ if (this.systemInformation.ipv4Address !== '')
2716
+ this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
2717
+ if (this.systemInformation.ipv6Address !== '')
2718
+ this.log.debug(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2719
+ });
2720
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2721
+ this.httpsServer.on('error', (error) => {
2722
+ this.log.error(`Frontend https server error listening on ${port}`);
2723
+ switch (error.code) {
2724
+ case 'EACCES':
2725
+ this.log.error(`Port ${port} requires elevated privileges`);
2726
+ break;
2727
+ case 'EADDRINUSE':
2728
+ this.log.error(`Port ${port} is already in use`);
2729
+ break;
2730
+ }
2731
+ process.exit(1);
2704
2732
  });
2705
2733
  }
2706
- this.webSocketServer.on('connection', (ws) => {
2707
- this.log.info('WebSocketServer client connected');
2734
+ // Createe a WebSocket server and attach it to the http server
2735
+ const wssPort = port;
2736
+ const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2737
+ this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2738
+ this.webSocketServer.on('connection', (ws, request) => {
2739
+ const clientIp = request.socket.remoteAddress;
2740
+ this.log.info(`WebSocketServer client ${clientIp} connected`);
2708
2741
  this.log.setGlobalCallback(this.wssSendMessage.bind(this));
2709
- this.log.debug('WebSocketServer activated logger callback');
2742
+ this.log.debug('WebSocketServer logger callback added');
2710
2743
  this.wssSendMessage('Matterbridge', 'info', 'WebSocketServer client connected to Matterbridge');
2711
2744
  ws.on('message', (message) => {
2712
- this.log.info(`WebSocket received message => ${message}`);
2745
+ this.log.debug(`WebSocket client message: ${message}`);
2713
2746
  });
2714
2747
  ws.on('close', () => {
2715
2748
  this.log.info('WebSocket client disconnected');
2716
2749
  if (this.webSocketServer?.clients.size === 0) {
2717
2750
  this.log.setGlobalCallback(undefined);
2718
- this.log.debug('WebSocket deactivated logger callback');
2751
+ this.log.debug('All WebSocket clients disconnected. WebSocketServer logger callback removed');
2719
2752
  }
2720
2753
  });
2721
2754
  ws.on('error', (error) => {
2722
- this.log.error(`WebSocket error: ${error}`);
2755
+ this.log.error(`WebSocket client error: ${error}`);
2723
2756
  });
2724
2757
  });
2758
+ this.webSocketServer.on('close', () => {
2759
+ this.log.debug(`WebSocketServer closed`);
2760
+ });
2761
+ this.webSocketServer.on('listening', () => {
2762
+ this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2763
+ });
2725
2764
  this.webSocketServer.on('error', (ws, error) => {
2726
2765
  this.log.error(`WebSocketServer error: ${error}`);
2727
- return;
2728
2766
  });
2767
+ /*
2768
+ // Create a WebSocket server
2769
+ const wssPort = 8284;
2770
+ const wssHost = `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2771
+ this.webSocketServer = new WebSocketServer({ port: wssPort, host: this.systemInformation.ipv4Address });
2772
+ this.log.debug(`WebSocket server created on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2773
+
2729
2774
  this.webSocketServer.on('listening', () => {
2730
- this.log.info(`WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2731
- return;
2775
+ this.log.info(`WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2776
+ return;
2732
2777
  });
2778
+ */
2779
+ /*
2733
2780
  // Serve React build directory
2734
2781
  this.expressApp = express();
2735
2782
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2783
+
2784
+ // Listen on HTTP
2785
+ this.expressServer = this.expressApp.listen(port, () => {
2786
+ this.log.info(`The frontend is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
2787
+ this.log.debug(`The frontend is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2788
+ });
2789
+
2790
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2791
+ this.expressServer.on('error', (error: any) => {
2792
+ this.log.error(`Frontend error listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
2793
+ switch (error.code) {
2794
+ case 'EACCES':
2795
+ this.log.error(`Port ${port} requires elevated privileges`);
2796
+ break;
2797
+ case 'EADDRINUSE':
2798
+ this.log.error(`Port ${port} is already in use`);
2799
+ break;
2800
+ }
2801
+ process.exit(1);
2802
+ });
2803
+ */
2736
2804
  // Endpoint to validate login code
2737
2805
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2738
2806
  const { password } = req.body;
@@ -2783,13 +2851,15 @@ export class Matterbridge extends EventEmitter {
2783
2851
  this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
2784
2852
  // this.matterbridgeInformation.matterbridgeFabricInfo = this.matterbridgeFabricInfo;
2785
2853
  const response = { wssHost, qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2786
- this.log.debug('Response:', debugStringify(response));
2854
+ // this.log.debug('Response:', debugStringify(response));
2787
2855
  res.json(response);
2788
2856
  });
2789
2857
  // Endpoint to provide plugins
2790
2858
  this.expressApp.get('/api/plugins', async (req, res) => {
2791
2859
  this.log.debug('The frontend sent /api/plugins');
2792
- res.json(await this.getBaseRegisteredPlugins(true));
2860
+ const response = await this.getBaseRegisteredPlugins(true);
2861
+ // this.log.debug('Response:', debugStringify(response));
2862
+ res.json(response);
2793
2863
  });
2794
2864
  // Endpoint to provide devices
2795
2865
  this.expressApp.get('/api/devices', (req, res) => {
@@ -2816,6 +2886,7 @@ export class Matterbridge extends EventEmitter {
2816
2886
  cluster: cluster,
2817
2887
  });
2818
2888
  });
2889
+ // this.log.debug('Response:', debugStringify(data));
2819
2890
  res.json(data);
2820
2891
  });
2821
2892
  // Endpoint to provide the cluster servers of the devices
@@ -2946,7 +3017,7 @@ export class Matterbridge extends EventEmitter {
2946
3017
  plugin.platform?.log.setLogDebug(this.debugEnabled);
2947
3018
  });
2948
3019
  }
2949
- // Handle the command reset from Settings
3020
+ // Handle the command unregister from Settings
2950
3021
  if (command === 'unregister') {
2951
3022
  await this.unregisterAndShutdownProcess();
2952
3023
  }
@@ -2958,7 +3029,7 @@ export class Matterbridge extends EventEmitter {
2958
3029
  if (command === 'factoryreset') {
2959
3030
  this.shutdownProcessAndFactoryReset(); // No await do it asyncronously
2960
3031
  }
2961
- // Handle the command restart from Header
3032
+ // Handle the command shutdown from Header
2962
3033
  if (command === 'shutdown') {
2963
3034
  this.shutdownProcess(); // No await do it asyncronously
2964
3035
  }
@@ -2980,7 +3051,7 @@ export class Matterbridge extends EventEmitter {
2980
3051
  }
2981
3052
  this.updateProcess();
2982
3053
  }
2983
- // Handle the command saveschema from Home
3054
+ // Handle the command saveconfig from Home
2984
3055
  if (command === 'saveconfig') {
2985
3056
  param = param.replace(/\*/g, '\\');
2986
3057
  this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
@@ -3030,6 +3101,7 @@ export class Matterbridge extends EventEmitter {
3030
3101
  this.registeredPlugins.push(plugin);
3031
3102
  await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
3032
3103
  this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge. Restart required.`);
3104
+ this.startPlugin(plugin, 'The plugin has been added to Matterbridge', true);
3033
3105
  }
3034
3106
  else {
3035
3107
  this.log.error(`Error adding plugin ${plg}${packageJsonPath}${er}`);
@@ -3047,8 +3119,10 @@ export class Matterbridge extends EventEmitter {
3047
3119
  if (index !== -1) {
3048
3120
  if (this.registeredPlugins[index].platform) {
3049
3121
  await this.registeredPlugins[index].platform?.onShutdown('The plugin has been removed.');
3050
- // await this.savePluginConfig(this.registeredPlugins[index]);
3051
3122
  }
3123
+ // Remove all devices from the plugin
3124
+ await this.removeAllBridgedDevices(this.registeredPlugins[index].name);
3125
+ // Remove the plugin from the registered plugins
3052
3126
  this.registeredPlugins.splice(index, 1);
3053
3127
  await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
3054
3128
  this.log.info(`Plugin ${plg}${param}${nf} removed from matterbridge`);
@@ -3065,6 +3139,7 @@ export class Matterbridge extends EventEmitter {
3065
3139
  const plugin = plugins.find((plugin) => plugin.name === param);
3066
3140
  if (plugin) {
3067
3141
  plugin.enabled = true;
3142
+ plugin.locked = undefined;
3068
3143
  plugin.error = undefined;
3069
3144
  plugin.loaded = undefined;
3070
3145
  plugin.started = undefined;
@@ -3072,6 +3147,7 @@ export class Matterbridge extends EventEmitter {
3072
3147
  plugin.connected = undefined;
3073
3148
  plugin.platform = undefined;
3074
3149
  plugin.registeredDevices = undefined;
3150
+ plugin.addedDevices = undefined;
3075
3151
  await this.nodeContext?.set('plugins', plugins);
3076
3152
  this.log.info(`Enabled plugin ${plg}${param}${nf}`);
3077
3153
  }
@@ -3096,10 +3172,13 @@ export class Matterbridge extends EventEmitter {
3096
3172
  const pluginToDisable = this.findPlugin(param);
3097
3173
  if (pluginToDisable) {
3098
3174
  if (pluginToDisable.platform) {
3099
- await pluginToDisable.platform.onShutdown('The plugin has been removed.');
3100
- // await this.savePluginConfig(pluginToDisable);
3175
+ await pluginToDisable.platform.onShutdown('The plugin has been disabled.');
3101
3176
  }
3177
+ // Remove all devices from the plugin
3178
+ this.log.info(`Unregistering devices for plugin ${plg}${pluginToDisable.name}${nf}...`);
3179
+ await this.removeAllBridgedDevices(pluginToDisable.name);
3102
3180
  pluginToDisable.enabled = false;
3181
+ pluginToDisable.locked = undefined;
3103
3182
  pluginToDisable.error = undefined;
3104
3183
  pluginToDisable.loaded = undefined;
3105
3184
  pluginToDisable.started = undefined;
@@ -3107,12 +3186,15 @@ export class Matterbridge extends EventEmitter {
3107
3186
  pluginToDisable.connected = undefined;
3108
3187
  pluginToDisable.platform = undefined;
3109
3188
  pluginToDisable.registeredDevices = undefined;
3189
+ pluginToDisable.addedDevices = undefined;
3190
+ // Save the plugins state in node storage
3110
3191
  const plugins = await this.nodeContext?.get('plugins');
3111
3192
  if (!plugins)
3112
3193
  return;
3113
3194
  const plugin = plugins.find((plugin) => plugin.name === param);
3114
3195
  if (plugin) {
3115
3196
  plugin.enabled = false;
3197
+ plugin.locked = undefined;
3116
3198
  plugin.error = undefined;
3117
3199
  plugin.loaded = undefined;
3118
3200
  plugin.started = undefined;
@@ -3120,6 +3202,7 @@ export class Matterbridge extends EventEmitter {
3120
3202
  plugin.connected = undefined;
3121
3203
  plugin.platform = undefined;
3122
3204
  plugin.registeredDevices = undefined;
3205
+ plugin.addedDevices = undefined;
3123
3206
  await this.nodeContext?.set('plugins', plugins);
3124
3207
  this.log.info(`Disabled plugin ${plg}${param}${nf}`);
3125
3208
  }
@@ -3127,46 +3210,12 @@ export class Matterbridge extends EventEmitter {
3127
3210
  }
3128
3211
  res.json({ message: 'Command received' });
3129
3212
  });
3130
- // Fallback for routing
3213
+ // Fallback for routing (must be the last route but it should not be used because the frontend is static)
3131
3214
  this.expressApp.get('*', (req, res) => {
3132
- this.log.debug('The frontend sent *', req.url);
3215
+ this.log.warn('The frontend sent:', req.url);
3216
+ this.log.warn('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
3133
3217
  res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
3134
3218
  });
3135
- if (!useHttps) {
3136
- // Listen on HTTP
3137
- this.expressServer = this.expressApp.listen(port, () => {
3138
- this.log.info(`The frontend is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
3139
- });
3140
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3141
- this.expressServer.on('error', (error) => {
3142
- this.log.error(`Frontend error listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
3143
- switch (error.code) {
3144
- case 'EACCES':
3145
- this.log.error(`Port ${port} requires elevated privileges`);
3146
- break;
3147
- case 'EADDRINUSE':
3148
- this.log.error(`Port ${port} is already in use`);
3149
- break;
3150
- default:
3151
- this.log.error(`Port ${port} requires elevated privileges`);
3152
- }
3153
- process.exit(1);
3154
- });
3155
- }
3156
- else {
3157
- // SSL certificate and private key paths
3158
- const options = {
3159
- cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.pem')), // Ensure the path is correct
3160
- key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.key')), // Ensure the path is correct
3161
- };
3162
- // Create HTTPS server
3163
- const httpsServer = https.createServer(options, this.expressApp);
3164
- // Specify the port to listen on, for example 443 for default HTTPS
3165
- const PORT = 443;
3166
- httpsServer.listen(PORT, () => {
3167
- this.log.info(`The frontend is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${PORT}${UNDERLINEOFF}${rs}`);
3168
- });
3169
- }
3170
3219
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
3171
3220
  }
3172
3221
  /**
@@ -3228,125 +3277,4 @@ export class Matterbridge extends EventEmitter {
3228
3277
  return attributes;
3229
3278
  }
3230
3279
  }
3231
- /*
3232
- TO IMPLEMENT
3233
-
3234
- import { spawn } from 'child_process';
3235
-
3236
- function restartProcess() {
3237
- // Spawn a new process
3238
- const newProcess = spawn(process.argv[0], process.argv.slice(1), {
3239
- detached: true,
3240
- stdio: 'inherit',
3241
- });
3242
-
3243
- // Handle errors
3244
- newProcess.on('error', (err) => {
3245
- console.error('Failed to start new process:', err);
3246
- });
3247
-
3248
- // Unreference the new process so that the current process can exit
3249
- newProcess.unref();
3250
-
3251
- // Exit the current process
3252
- cleanup();
3253
- process.exit();
3254
- }
3255
-
3256
- import React from 'react';
3257
- import Form from "@rjsf/core";
3258
-
3259
- const schema = {
3260
- title: "Todo",
3261
- type: "object",
3262
- required: ["title"],
3263
- properties: {
3264
- title: {type: "string", title: "Title", default: "A new task"},
3265
- done: {type: "boolean", title: "Done?", default: false}
3266
- }
3267
- };
3268
-
3269
- const log = (type) => console.log.bind(console, type);
3270
-
3271
- function Todo() {
3272
- return (
3273
- <Form schema={schema}
3274
- onChange={log("changed")}
3275
- onSubmit={log("submitted")}
3276
- onError={log("errors")} />
3277
- );
3278
- }
3279
-
3280
- export default Todo;
3281
-
3282
- /*
3283
- How frontend was created
3284
- npx create-react-app matterbridge-frontend
3285
- cd matterbridge-frontend
3286
- npm install react-router-dom
3287
-
3288
- Success! Created frontend at C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend
3289
- Inside that directory, you can run several commands:
3290
-
3291
- npm start
3292
- Starts the development server.
3293
-
3294
- npm run build
3295
- Bundles the app into static files for production.
3296
-
3297
- npm test
3298
- Starts the test runner.
3299
-
3300
- npm run eject
3301
- Removes this tool and copies build dependencies, configuration files
3302
- and scripts into the app directory. If you do this, you can’t go back!
3303
-
3304
- We suggest that you begin by typing:
3305
-
3306
- cd frontend
3307
- npm start
3308
-
3309
- Happy hacking!
3310
- PS C:\Users\lligu\OneDrive\GitHub\matterbridge> cd frontend
3311
- PS C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend> npm run build
3312
-
3313
- > frontend@0.1.0 build
3314
- > react-scripts build
3315
-
3316
- Creating an optimized production build...
3317
- One of your dependencies, babel-preset-react-app, is importing the
3318
- "@babel/plugin-proposal-private-property-in-object" package without
3319
- declaring it in its dependencies. This is currently working because
3320
- "@babel/plugin-proposal-private-property-in-object" is already in your
3321
- node_modules folder for unrelated reasons, but it may break at any time.
3322
-
3323
- babel-preset-react-app is part of the create-react-app project, which
3324
- is not maintianed anymore. It is thus unlikely that this bug will
3325
- ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
3326
- your devDependencies to work around this error. This will make this message
3327
- go away.
3328
-
3329
- Compiled successfully.
3330
-
3331
- File sizes after gzip:
3332
-
3333
- 46.65 kB build\static\js\main.9b7ec296.js
3334
- 1.77 kB build\static\js\453.8ab44547.chunk.js
3335
- 513 B build\static\css\main.f855e6bc.css
3336
-
3337
- The project was built assuming it is hosted at /.
3338
- You can control this with the homepage field in your package.json.
3339
-
3340
- The build folder is ready to be deployed.
3341
- You may serve it with a static server:
3342
-
3343
- npm install -g serve
3344
- serve -s build
3345
-
3346
- Find out more about deployment here:
3347
-
3348
- https://cra.link/deployment
3349
-
3350
- PS C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend>
3351
- */
3352
3280
  //# sourceMappingURL=matterbridge.js.map