matterbridge 1.1.12 → 1.2.0

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 (43) hide show
  1. package/CHANGELOG.md +7 -3
  2. package/README.md +4 -4
  3. package/dist/AirQualityCluster.d.ts.map +1 -1
  4. package/dist/AirQualityCluster.js +1 -10
  5. package/dist/AirQualityCluster.js.map +1 -1
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +0 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/matterbridge.d.ts +89 -13
  11. package/dist/matterbridge.d.ts.map +1 -1
  12. package/dist/matterbridge.js +154 -29
  13. package/dist/matterbridge.js.map +1 -1
  14. package/dist/matterbridgeAccessoryPlatform.d.ts +3 -2
  15. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  16. package/dist/matterbridgeAccessoryPlatform.js +3 -2
  17. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  18. package/dist/matterbridgeDevice.d.ts +60 -0
  19. package/dist/matterbridgeDevice.d.ts.map +1 -1
  20. package/dist/matterbridgeDevice.js +60 -0
  21. package/dist/matterbridgeDevice.js.map +1 -1
  22. package/dist/matterbridgeDynamicPlatform.d.ts +3 -2
  23. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  24. package/dist/matterbridgeDynamicPlatform.js +3 -2
  25. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  26. package/dist/matterbridgePlatform.d.ts +10 -11
  27. package/dist/matterbridgePlatform.d.ts.map +1 -1
  28. package/dist/matterbridgePlatform.js +10 -58
  29. package/dist/matterbridgePlatform.js.map +1 -1
  30. package/frontend/build/asset-manifest.json +6 -6
  31. package/frontend/build/index.html +1 -1
  32. package/frontend/build/static/css/{main.70102d98.css → main.b996bc4e.css} +2 -2
  33. package/frontend/build/static/css/main.b996bc4e.css.map +1 -0
  34. package/frontend/build/static/js/{main.5d39b100.js → main.657bca15.js} +3 -3
  35. package/frontend/build/static/js/main.657bca15.js.map +1 -0
  36. package/package.json +5 -3
  37. package/dist/ColorControlServer.d.ts +0 -86
  38. package/dist/ColorControlServer.d.ts.map +0 -1
  39. package/dist/ColorControlServer.js +0 -102
  40. package/dist/ColorControlServer.js.map +0 -1
  41. package/frontend/build/static/css/main.70102d98.css.map +0 -1
  42. package/frontend/build/static/js/main.5d39b100.js.map +0 -1
  43. /package/frontend/build/static/js/{main.5d39b100.js.LICENSE.txt → main.657bca15.js.LICENSE.txt} +0 -0
@@ -4,7 +4,7 @@
4
4
  * @file matterbridge.ts
5
5
  * @author Luca Liguori
6
6
  * @date 2023-12-29
7
- * @version 1.1.1
7
+ * @version 1.2.0
8
8
  *
9
9
  * Copyright 2023, 2024 Luca Liguori.
10
10
  *
@@ -102,7 +102,9 @@ export class Matterbridge extends EventEmitter {
102
102
  /**
103
103
  * Loads an instance of the Matterbridge class.
104
104
  * If an instance already exists, return that instance.
105
- * @returns The loaded instance of the Matterbridge class.
105
+ *
106
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
107
+ * @returns The loaded Matterbridge instance.
106
108
  */
107
109
  static async loadInstance(initialize = false) {
108
110
  if (!Matterbridge.instance) {
@@ -129,10 +131,6 @@ export class Matterbridge extends EventEmitter {
129
131
  * @returns A Promise that resolves when the initialization is complete.
130
132
  */
131
133
  async initialize() {
132
- /*
133
- const wtf = await import('wtfnode');
134
- wtf.dump();
135
- */
136
134
  // Display the help
137
135
  if (hasParameter('help')) {
138
136
  // eslint-disable-next-line no-console
@@ -163,7 +161,7 @@ export class Matterbridge extends EventEmitter {
163
161
  await this.logNodeAndSystemInfo();
164
162
  this.log.info(
165
163
  // eslint-disable-next-line max-len
166
- `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
164
+ `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
167
165
  // check node version and throw error
168
166
  requireMinNodeVersion(18);
169
167
  // register SIGINT SIGTERM signal handlers
@@ -282,12 +280,14 @@ export class Matterbridge extends EventEmitter {
282
280
  this.bridgeMode = 'childbridge';
283
281
  MatterbridgeDevice.bridgeMode = 'childbridge';
284
282
  await this.testStartMatterBridge(); // No await do it asyncronously
283
+ return;
285
284
  }
286
285
  if (hasParameter('controller')) {
287
286
  this.bridgeMode = 'controller';
288
287
  this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
289
288
  this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
290
289
  await this.startMatterBridge();
290
+ return;
291
291
  }
292
292
  if (hasParameter('bridge')) {
293
293
  this.bridgeMode = 'bridge';
@@ -306,6 +306,7 @@ export class Matterbridge extends EventEmitter {
306
306
  this.loadPlugin(plugin); // No await do it asyncronously
307
307
  }
308
308
  await this.startMatterBridge();
309
+ return;
309
310
  }
310
311
  if (hasParameter('childbridge')) {
311
312
  this.bridgeMode = 'childbridge';
@@ -324,8 +325,14 @@ export class Matterbridge extends EventEmitter {
324
325
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
325
326
  }
326
327
  await this.startMatterBridge();
328
+ return;
327
329
  }
328
330
  }
331
+ /**
332
+ * Resolves the name of a plugin by loading and parsing its package.json file.
333
+ * @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
334
+ * @returns The path to the resolved package.json file, or null if the package.json file is not found or does not contain a name.
335
+ */
329
336
  async resolvePluginName(pluginPath) {
330
337
  if (!pluginPath.endsWith('package.json'))
331
338
  pluginPath = path.join(pluginPath, 'package.json');
@@ -451,14 +458,14 @@ export class Matterbridge extends EventEmitter {
451
458
  * Restarts the process by spawning a new process and exiting the current process.
452
459
  */
453
460
  async restartProcess() {
454
- //this.log.info('Restarting still not implemented');
455
- //return;
456
461
  await this.cleanup('restarting...', true);
457
462
  this.hasCleanupStarted = false;
458
463
  }
459
464
  /**
460
- * Performs cleanup operations before shutting down Matterbridge.
461
- * @param message - The reason for the cleanup.
465
+ * Cleans up the Matterbridge instance.
466
+ * @param message - The cleanup message.
467
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
468
+ * @returns A promise that resolves when the cleanup is completed.
462
469
  */
463
470
  async cleanup(message, restart = false) {
464
471
  if (!this.hasCleanupStarted) {
@@ -468,10 +475,10 @@ export class Matterbridge extends EventEmitter {
468
475
  process.removeAllListeners('SIGTERM');
469
476
  // Calling the shutdown functions with a reason
470
477
  for (const plugin of this.registeredPlugins) {
471
- if (plugin.platform)
478
+ if (plugin.platform) {
472
479
  await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
473
- if (plugin.platform)
474
- await plugin.platform.saveConfig();
480
+ await this.savePluginConfig(plugin);
481
+ }
475
482
  }
476
483
  // Set reachability to false
477
484
  /*
@@ -631,6 +638,12 @@ export class Matterbridge extends EventEmitter {
631
638
  this.log.info(`Registered bridged device ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
632
639
  }
633
640
  }
641
+ /**
642
+ * Removes a bridged device from the Matterbridge.
643
+ * @param pluginName - The name of the plugin.
644
+ * @param device - The device to be removed.
645
+ * @returns A Promise that resolves when the device is successfully removed.
646
+ */
634
647
  async removeBridgedDevice(pluginName, device) {
635
648
  if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
636
649
  this.log.error(`Removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
@@ -647,12 +660,10 @@ export class Matterbridge extends EventEmitter {
647
660
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} aggregator not found`);
648
661
  return;
649
662
  }
650
- /*
651
663
  if (this.bridgeMode === 'childbridge' && !plugin.connected) {
652
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} not connected`);
653
- return;
664
+ this.log.warn(`Not removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) plugin ${plg}${pluginName}${wr} not connected`);
665
+ return;
654
666
  }
655
- */
656
667
  // Register and add the device to matterbridge aggregator in bridge mode
657
668
  if (this.bridgeMode === 'bridge') {
658
669
  this.matterAggregator.removeBridgedDevice(device);
@@ -830,6 +841,92 @@ export class Matterbridge extends EventEmitter {
830
841
  }
831
842
  */
832
843
  }
844
+ /**
845
+ * Loads the configuration for a plugin.
846
+ * If the configuration file exists, it reads the file and returns the parsed JSON data.
847
+ * If the configuration file does not exist, it creates a new file with default configuration and returns it.
848
+ * If any error occurs during file access or creation, it logs an error and rejects the promise with the error.
849
+ *
850
+ * @param plugin - The plugin for which to load the configuration.
851
+ * @returns A promise that resolves to the loaded or created configuration.
852
+ */
853
+ async loadPluginConfig(plugin) {
854
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
855
+ try {
856
+ await fs.access(configFile);
857
+ const data = await fs.readFile(configFile, 'utf8');
858
+ this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, JSON.parse(data));
859
+ return JSON.parse(data);
860
+ }
861
+ catch (err) {
862
+ if (err instanceof Error) {
863
+ const nodeErr = err;
864
+ if (nodeErr.code === 'ENOENT') {
865
+ try {
866
+ await this.writeFile(configFile, JSON.stringify({ name: plugin.name, type: plugin.type }, null, 2));
867
+ this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, { name: plugin.name, type: plugin.type });
868
+ return { name: plugin.name, type: plugin.type };
869
+ }
870
+ catch (err) {
871
+ this.log.error(`Error creating config file ${configFile}: ${err}`);
872
+ return Promise.reject(err);
873
+ }
874
+ }
875
+ else {
876
+ this.log.error(`Error accessing config file ${configFile}: ${err}`);
877
+ return Promise.reject(err);
878
+ }
879
+ }
880
+ return Promise.reject(err);
881
+ }
882
+ }
883
+ /**
884
+ * Saves the configuration of a registered plugin.
885
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration needs to be saved.
886
+ * @returns {Promise<void>} - A promise that resolves when the configuration is successfully saved.
887
+ * @throws {Error} - If the plugin's configuration is not found or if there is an error while saving the configuration.
888
+ */
889
+ async savePluginConfig(plugin) {
890
+ if (!plugin.platform?.config) {
891
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`);
892
+ return Promise.reject(new Error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`));
893
+ }
894
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
895
+ try {
896
+ await this.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2));
897
+ this.log.debug(`Saved config file: ${configFile}.\nConfig:${rs}\n`, plugin.platform.config);
898
+ }
899
+ catch (err) {
900
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: ${err}`);
901
+ return Promise.reject(err);
902
+ }
903
+ }
904
+ /**
905
+ * Writes data to a file.
906
+ *
907
+ * @param {string} filePath - The path of the file to write to.
908
+ * @param {string} data - The data to write to the file.
909
+ * @returns {Promise<void>} - A promise that resolves when the data is successfully written to the file.
910
+ */
911
+ async writeFile(filePath, data) {
912
+ // Write the data to a file
913
+ await fs
914
+ .writeFile(`${filePath}`, data, 'utf8')
915
+ .then(() => {
916
+ this.log.debug(`Successfully wrote to ${filePath}`);
917
+ })
918
+ .catch((error) => {
919
+ this.log.error(`Error writing to ${filePath}:`, error);
920
+ });
921
+ }
922
+ /**
923
+ * Starts a plugin.
924
+ *
925
+ * @param {RegisteredPlugin} plugin - The plugin to start.
926
+ * @param {string} [message] - Optional message to pass to the plugin's onStart method.
927
+ * @param {boolean} [configure=false] - Indicates whether to configure the plugin after starting.
928
+ * @returns {Promise<void>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
929
+ */
833
930
  async startPlugin(plugin, message, configure = false) {
834
931
  if (!plugin.loaded || !plugin.platform) {
835
932
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`);
@@ -860,6 +957,12 @@ export class Matterbridge extends EventEmitter {
860
957
  return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
861
958
  }
862
959
  }
960
+ /**
961
+ * Configures a plugin.
962
+ *
963
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
964
+ * @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
965
+ */
863
966
  async configurePlugin(plugin) {
864
967
  if (!plugin.loaded || !plugin.started || !plugin.platform) {
865
968
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`);
@@ -888,6 +991,14 @@ export class Matterbridge extends EventEmitter {
888
991
  return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
889
992
  }
890
993
  }
994
+ /**
995
+ * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
996
+ * @param plugin - The plugin to load.
997
+ * @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
998
+ * @param message - Optional message to pass to the plugin when starting.
999
+ * @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
1000
+ * @throws An error if the plugin is not enabled, already loaded, or fails to load.
1001
+ */
891
1002
  async loadPlugin(plugin, start = false, message = '') {
892
1003
  if (!plugin.enabled) {
893
1004
  this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
@@ -910,8 +1021,10 @@ export class Matterbridge extends EventEmitter {
910
1021
  this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
911
1022
  // Call the default export function of the plugin, passing this MatterBridge instance
912
1023
  if (pluginInstance.default) {
913
- const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }));
1024
+ const config = await this.loadPluginConfig(plugin);
1025
+ const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }), config);
914
1026
  platform.name = packageJson.name;
1027
+ platform.config = config;
915
1028
  plugin.name = packageJson.name;
916
1029
  plugin.description = packageJson.description;
917
1030
  plugin.version = packageJson.version;
@@ -922,7 +1035,6 @@ export class Matterbridge extends EventEmitter {
922
1035
  plugin.registeredDevices = 0;
923
1036
  plugin.addedDevices = 0;
924
1037
  await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
925
- await platform.loadConfig();
926
1038
  this.log.info(`Loaded plugin ${plg}${plugin.name}${db} type ${typ}${platform.type}${db} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
927
1039
  if (start)
928
1040
  this.startPlugin(plugin, message); // No await do it asyncronously
@@ -1113,6 +1225,10 @@ export class Matterbridge extends EventEmitter {
1113
1225
  }, 1000);
1114
1226
  }
1115
1227
  }
1228
+ /**
1229
+ * Starts the Matter server.
1230
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
1231
+ */
1116
1232
  async startMatterServer() {
1117
1233
  if (!this.matterServer) {
1118
1234
  this.log.error('No matter server initialized');
@@ -1182,15 +1298,12 @@ export class Matterbridge extends EventEmitter {
1182
1298
  return storageContext;
1183
1299
  }
1184
1300
  /**
1185
- * Shows the commissioning QR code for a given commissioning server, storage context, and name.
1186
- * If any of the parameters are missing, the method returns early.
1187
- * If the commissioning server is not commissioned, it logs the QR code and pairing code.
1188
- * If the commissioning server is already commissioned, it waits for controllers to connect.
1189
- * If the bridge mode is 'childbridge', it sets the 'paired' property of the plugin to true.
1190
- *
1191
- * @param commissioningServer - The commissioning server to show the QR code for.
1192
- * @param storageContext - The storage context to store the pairing codes.
1193
- * @param pluginName - The name of the commissioning server.
1301
+ * Shows the commissioning QR code for a given plugin.
1302
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
1303
+ * @param {StorageContext} storageContext - The storage context instance.
1304
+ * @param {NodeStorage} nodeContext - The node storage instance.
1305
+ * @param {string} pluginName - The name of the plugin.
1306
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
1194
1307
  */
1195
1308
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1196
1309
  if (!commissioningServer || !storageContext || !pluginName)
@@ -1207,6 +1320,8 @@ export class Matterbridge extends EventEmitter {
1207
1320
  if (pluginName !== 'Matterbridge') {
1208
1321
  const plugin = this.findPlugin(pluginName);
1209
1322
  if (plugin) {
1323
+ plugin.qrPairingCode = qrPairingCode;
1324
+ plugin.manualPairingCode = manualPairingCode;
1210
1325
  plugin.paired = false;
1211
1326
  }
1212
1327
  }
@@ -1597,6 +1712,11 @@ export class Matterbridge extends EventEmitter {
1597
1712
  const cmdArgs = process.argv.slice(2).join(' ');
1598
1713
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
1599
1714
  }
1715
+ /**
1716
+ * Retrieves an array of base registered plugins.
1717
+ *
1718
+ * @returns {BaseRegisteredPlugin[]} An array of base registered plugins.
1719
+ */
1600
1720
  getBaseRegisteredPlugins() {
1601
1721
  const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
1602
1722
  path: plugin.path,
@@ -1617,6 +1737,10 @@ export class Matterbridge extends EventEmitter {
1617
1737
  }));
1618
1738
  return baseRegisteredPlugins;
1619
1739
  }
1740
+ /**
1741
+ * Retrieves an array of base registered devices from the registered plugins.
1742
+ * @returns {BaseRegisteredPlugin[]} An array of base registered devices.
1743
+ */
1620
1744
  getBaseRegisteredDevices() {
1621
1745
  const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
1622
1746
  path: plugin.path,
@@ -1648,6 +1772,7 @@ export class Matterbridge extends EventEmitter {
1648
1772
  this.expressApp.get('/api/qr-code', (req, res) => {
1649
1773
  this.log.debug('The frontend sent /api/qr-code');
1650
1774
  if (!this.matterbridgeContext) {
1775
+ this.log.error('/api/qr-code matterbridgeContext not found');
1651
1776
  res.json([]);
1652
1777
  return;
1653
1778
  }