matterbridge 1.1.12 → 1.2.1

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 +22 -3
  2. package/README.md +52 -11
  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 +279 -53
  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.61f6cf42.css} +2 -2
  33. package/frontend/build/static/css/main.61f6cf42.css.map +1 -0
  34. package/frontend/build/static/js/main.e3553a4d.js +3 -0
  35. package/frontend/build/static/js/main.e3553a4d.js.map +1 -0
  36. package/package.json +8 -5
  37. package/Screenshot devices page.png +0 -0
  38. package/Screenshot home page.png +0 -0
  39. package/dist/ColorControlServer.d.ts +0 -86
  40. package/dist/ColorControlServer.d.ts.map +0 -1
  41. package/dist/ColorControlServer.js +0 -102
  42. package/dist/ColorControlServer.js.map +0 -1
  43. package/frontend/build/static/css/main.70102d98.css.map +0 -1
  44. package/frontend/build/static/js/main.5d39b100.js +0 -3
  45. package/frontend/build/static/js/main.5d39b100.js.map +0 -1
  46. /package/frontend/build/static/js/{main.5d39b100.js.LICENSE.txt → main.e3553a4d.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,20 +102,18 @@ 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) {
109
111
  // eslint-disable-next-line no-console
110
- console.log('Matterbridge instance does not exists');
112
+ console.log(wr + 'Matterbridge instance does not exists!', initialize ? 'Initializing...' : 'Not initializing...', rs);
111
113
  Matterbridge.instance = new Matterbridge();
112
114
  if (initialize)
113
115
  await Matterbridge.instance.initialize();
114
116
  }
115
- else {
116
- // eslint-disable-next-line no-console
117
- console.log('Matterbridge instance already exists');
118
- }
119
117
  return Matterbridge.instance;
120
118
  }
121
119
  /**
@@ -129,10 +127,6 @@ export class Matterbridge extends EventEmitter {
129
127
  * @returns A Promise that resolves when the initialization is complete.
130
128
  */
131
129
  async initialize() {
132
- /*
133
- const wtf = await import('wtfnode');
134
- wtf.dump();
135
- */
136
130
  // Display the help
137
131
  if (hasParameter('help')) {
138
132
  // eslint-disable-next-line no-console
@@ -154,28 +148,19 @@ export class Matterbridge extends EventEmitter {
154
148
  - disable [plugin name]: disable the globally installed plugin with the given name\n`);
155
149
  process.exit(0);
156
150
  }
157
- // set Matterbridge logger
151
+ // Set Matterbridge logger
158
152
  if (hasParameter('debug'))
159
153
  this.debugEnabled = true;
160
154
  this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
161
155
  this.log.debug('Matterbridge is starting...');
162
- // log system info and create .matterbridge directory
163
- await this.logNodeAndSystemInfo();
164
- this.log.info(
165
- // 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}`);
167
- // check node version and throw error
168
- requireMinNodeVersion(18);
169
- // register SIGINT SIGTERM signal handlers
170
- this.registerSignalHandlers();
171
- // set matter.js logger level and format
172
- Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
173
- Logger.format = Format.ANSI;
174
156
  // Initialize NodeStorage
157
+ this.homeDirectory = os.homedir();
158
+ this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
175
159
  this.log.debug('Creating node storage manager');
176
160
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
177
161
  this.log.debug('Creating node storage context for matterbridge');
178
162
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
163
+ // Get the plugins from node storage
179
164
  this.registeredPlugins = await this.nodeContext.get('plugins', []);
180
165
  for (const plugin of this.registeredPlugins) {
181
166
  this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
@@ -187,6 +172,43 @@ export class Matterbridge extends EventEmitter {
187
172
  await plugin.nodeContext.set('description', plugin.description);
188
173
  await plugin.nodeContext.set('author', plugin.author);
189
174
  }
175
+ if (hasParameter('logstorage')) {
176
+ await this.nodeContext.logStorage();
177
+ for (const plugin of this.registeredPlugins) {
178
+ await plugin.nodeContext?.logStorage();
179
+ }
180
+ }
181
+ // Log system info and create .matterbridge directory
182
+ await this.logNodeAndSystemInfo();
183
+ this.log.info(
184
+ // eslint-disable-next-line max-len
185
+ `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}`);
186
+ // Check node version and throw error
187
+ requireMinNodeVersion(18);
188
+ // Register SIGINT SIGTERM signal handlers
189
+ this.registerSignalHandlers();
190
+ // Set matter.js logger level and format
191
+ Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
192
+ Logger.format = Format.ANSI;
193
+ // Initialize NodeStorage
194
+ /*
195
+ this.log.debug('Creating node storage manager');
196
+ this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
197
+ this.log.debug('Creating node storage context for matterbridge');
198
+ this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
199
+ // Get the plugins from node storage
200
+ this.registeredPlugins = await this.nodeContext.get<RegisteredPlugin[]>('plugins', []);
201
+ for (const plugin of this.registeredPlugins) {
202
+ this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
203
+ plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
204
+ await plugin.nodeContext.set<string>('name', plugin.name);
205
+ await plugin.nodeContext.set<string>('type', plugin.type);
206
+ await plugin.nodeContext.set<string>('path', plugin.path);
207
+ await plugin.nodeContext.set<string>('version', plugin.version);
208
+ await plugin.nodeContext.set<string>('description', plugin.description);
209
+ await plugin.nodeContext.set<string>('author', plugin.author);
210
+ }
211
+ */
190
212
  // Parse command line
191
213
  this.parseCommandLine();
192
214
  }
@@ -282,12 +304,14 @@ export class Matterbridge extends EventEmitter {
282
304
  this.bridgeMode = 'childbridge';
283
305
  MatterbridgeDevice.bridgeMode = 'childbridge';
284
306
  await this.testStartMatterBridge(); // No await do it asyncronously
307
+ return;
285
308
  }
286
309
  if (hasParameter('controller')) {
287
310
  this.bridgeMode = 'controller';
288
311
  this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
289
312
  this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
290
313
  await this.startMatterBridge();
314
+ return;
291
315
  }
292
316
  if (hasParameter('bridge')) {
293
317
  this.bridgeMode = 'bridge';
@@ -306,6 +330,7 @@ export class Matterbridge extends EventEmitter {
306
330
  this.loadPlugin(plugin); // No await do it asyncronously
307
331
  }
308
332
  await this.startMatterBridge();
333
+ return;
309
334
  }
310
335
  if (hasParameter('childbridge')) {
311
336
  this.bridgeMode = 'childbridge';
@@ -324,8 +349,14 @@ export class Matterbridge extends EventEmitter {
324
349
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
325
350
  }
326
351
  await this.startMatterBridge();
352
+ return;
327
353
  }
328
354
  }
355
+ /**
356
+ * Resolves the name of a plugin by loading and parsing its package.json file.
357
+ * @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
358
+ * @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.
359
+ */
329
360
  async resolvePluginName(pluginPath) {
330
361
  if (!pluginPath.endsWith('package.json'))
331
362
  pluginPath = path.join(pluginPath, 'package.json');
@@ -451,14 +482,14 @@ export class Matterbridge extends EventEmitter {
451
482
  * Restarts the process by spawning a new process and exiting the current process.
452
483
  */
453
484
  async restartProcess() {
454
- //this.log.info('Restarting still not implemented');
455
- //return;
456
485
  await this.cleanup('restarting...', true);
457
486
  this.hasCleanupStarted = false;
458
487
  }
459
488
  /**
460
- * Performs cleanup operations before shutting down Matterbridge.
461
- * @param message - The reason for the cleanup.
489
+ * Cleans up the Matterbridge instance.
490
+ * @param message - The cleanup message.
491
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
492
+ * @returns A promise that resolves when the cleanup is completed.
462
493
  */
463
494
  async cleanup(message, restart = false) {
464
495
  if (!this.hasCleanupStarted) {
@@ -468,10 +499,10 @@ export class Matterbridge extends EventEmitter {
468
499
  process.removeAllListeners('SIGTERM');
469
500
  // Calling the shutdown functions with a reason
470
501
  for (const plugin of this.registeredPlugins) {
471
- if (plugin.platform)
502
+ if (plugin.platform) {
472
503
  await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
473
- if (plugin.platform)
474
- await plugin.platform.saveConfig();
504
+ await this.savePluginConfig(plugin);
505
+ }
475
506
  }
476
507
  // Set reachability to false
477
508
  /*
@@ -631,6 +662,12 @@ export class Matterbridge extends EventEmitter {
631
662
  this.log.info(`Registered bridged device ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
632
663
  }
633
664
  }
665
+ /**
666
+ * Removes a bridged device from the Matterbridge.
667
+ * @param pluginName - The name of the plugin.
668
+ * @param device - The device to be removed.
669
+ * @returns A Promise that resolves when the device is successfully removed.
670
+ */
634
671
  async removeBridgedDevice(pluginName, device) {
635
672
  if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
636
673
  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 +684,10 @@ export class Matterbridge extends EventEmitter {
647
684
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} aggregator not found`);
648
685
  return;
649
686
  }
650
- /*
651
687
  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;
688
+ this.log.warn(`Error removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) plugin ${plg}${pluginName}${wr} not connected`);
689
+ return;
654
690
  }
655
- */
656
691
  // Register and add the device to matterbridge aggregator in bridge mode
657
692
  if (this.bridgeMode === 'bridge') {
658
693
  this.matterAggregator.removeBridgedDevice(device);
@@ -662,11 +697,11 @@ export class Matterbridge extends EventEmitter {
662
697
  return;
663
698
  }
664
699
  });
700
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
665
701
  if (plugin.registeredDevices !== undefined)
666
702
  plugin.registeredDevices--;
667
703
  if (plugin.addedDevices !== undefined)
668
704
  plugin.addedDevices--;
669
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
670
705
  }
671
706
  // Only register the device in childbridge mode
672
707
  if (this.bridgeMode === 'childbridge') {
@@ -830,6 +865,92 @@ export class Matterbridge extends EventEmitter {
830
865
  }
831
866
  */
832
867
  }
868
+ /**
869
+ * Loads the configuration for a plugin.
870
+ * If the configuration file exists, it reads the file and returns the parsed JSON data.
871
+ * If the configuration file does not exist, it creates a new file with default configuration and returns it.
872
+ * If any error occurs during file access or creation, it logs an error and rejects the promise with the error.
873
+ *
874
+ * @param plugin - The plugin for which to load the configuration.
875
+ * @returns A promise that resolves to the loaded or created configuration.
876
+ */
877
+ async loadPluginConfig(plugin) {
878
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
879
+ try {
880
+ await fs.access(configFile);
881
+ const data = await fs.readFile(configFile, 'utf8');
882
+ this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, JSON.parse(data));
883
+ return JSON.parse(data);
884
+ }
885
+ catch (err) {
886
+ if (err instanceof Error) {
887
+ const nodeErr = err;
888
+ if (nodeErr.code === 'ENOENT') {
889
+ try {
890
+ await this.writeFile(configFile, JSON.stringify({ name: plugin.name, type: plugin.type }, null, 2));
891
+ this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, { name: plugin.name, type: plugin.type });
892
+ return { name: plugin.name, type: plugin.type };
893
+ }
894
+ catch (err) {
895
+ this.log.error(`Error creating config file ${configFile}: ${err}`);
896
+ return Promise.reject(err);
897
+ }
898
+ }
899
+ else {
900
+ this.log.error(`Error accessing config file ${configFile}: ${err}`);
901
+ return Promise.reject(err);
902
+ }
903
+ }
904
+ return Promise.reject(err);
905
+ }
906
+ }
907
+ /**
908
+ * Saves the configuration of a registered plugin.
909
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration needs to be saved.
910
+ * @returns {Promise<void>} - A promise that resolves when the configuration is successfully saved.
911
+ * @throws {Error} - If the plugin's configuration is not found or if there is an error while saving the configuration.
912
+ */
913
+ async savePluginConfig(plugin) {
914
+ if (!plugin.platform?.config) {
915
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`);
916
+ return Promise.reject(new Error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`));
917
+ }
918
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
919
+ try {
920
+ await this.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2));
921
+ this.log.debug(`Saved config file: ${configFile}.\nConfig:${rs}\n`, plugin.platform.config);
922
+ }
923
+ catch (err) {
924
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: ${err}`);
925
+ return Promise.reject(err);
926
+ }
927
+ }
928
+ /**
929
+ * Writes data to a file.
930
+ *
931
+ * @param {string} filePath - The path of the file to write to.
932
+ * @param {string} data - The data to write to the file.
933
+ * @returns {Promise<void>} - A promise that resolves when the data is successfully written to the file.
934
+ */
935
+ async writeFile(filePath, data) {
936
+ // Write the data to a file
937
+ await fs
938
+ .writeFile(`${filePath}`, data, 'utf8')
939
+ .then(() => {
940
+ this.log.debug(`Successfully wrote to ${filePath}`);
941
+ })
942
+ .catch((error) => {
943
+ this.log.error(`Error writing to ${filePath}:`, error);
944
+ });
945
+ }
946
+ /**
947
+ * Starts a plugin.
948
+ *
949
+ * @param {RegisteredPlugin} plugin - The plugin to start.
950
+ * @param {string} [message] - Optional message to pass to the plugin's onStart method.
951
+ * @param {boolean} [configure=false] - Indicates whether to configure the plugin after starting.
952
+ * @returns {Promise<void>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
953
+ */
833
954
  async startPlugin(plugin, message, configure = false) {
834
955
  if (!plugin.loaded || !plugin.platform) {
835
956
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`);
@@ -860,6 +981,12 @@ export class Matterbridge extends EventEmitter {
860
981
  return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
861
982
  }
862
983
  }
984
+ /**
985
+ * Configures a plugin.
986
+ *
987
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
988
+ * @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
989
+ */
863
990
  async configurePlugin(plugin) {
864
991
  if (!plugin.loaded || !plugin.started || !plugin.platform) {
865
992
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`);
@@ -888,6 +1015,14 @@ export class Matterbridge extends EventEmitter {
888
1015
  return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
889
1016
  }
890
1017
  }
1018
+ /**
1019
+ * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
1020
+ * @param plugin - The plugin to load.
1021
+ * @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
1022
+ * @param message - Optional message to pass to the plugin when starting.
1023
+ * @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
1024
+ * @throws An error if the plugin is not enabled, already loaded, or fails to load.
1025
+ */
891
1026
  async loadPlugin(plugin, start = false, message = '') {
892
1027
  if (!plugin.enabled) {
893
1028
  this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
@@ -910,8 +1045,10 @@ export class Matterbridge extends EventEmitter {
910
1045
  this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
911
1046
  // Call the default export function of the plugin, passing this MatterBridge instance
912
1047
  if (pluginInstance.default) {
913
- const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }));
1048
+ const config = await this.loadPluginConfig(plugin);
1049
+ const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }), config);
914
1050
  platform.name = packageJson.name;
1051
+ platform.config = config;
915
1052
  plugin.name = packageJson.name;
916
1053
  plugin.description = packageJson.description;
917
1054
  plugin.version = packageJson.version;
@@ -922,7 +1059,6 @@ export class Matterbridge extends EventEmitter {
922
1059
  plugin.registeredDevices = 0;
923
1060
  plugin.addedDevices = 0;
924
1061
  await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
925
- await platform.loadConfig();
926
1062
  this.log.info(`Loaded plugin ${plg}${plugin.name}${db} type ${typ}${platform.type}${db} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
927
1063
  if (start)
928
1064
  this.startPlugin(plugin, message); // No await do it asyncronously
@@ -1113,6 +1249,10 @@ export class Matterbridge extends EventEmitter {
1113
1249
  }, 1000);
1114
1250
  }
1115
1251
  }
1252
+ /**
1253
+ * Starts the Matter server.
1254
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
1255
+ */
1116
1256
  async startMatterServer() {
1117
1257
  if (!this.matterServer) {
1118
1258
  this.log.error('No matter server initialized');
@@ -1182,15 +1322,12 @@ export class Matterbridge extends EventEmitter {
1182
1322
  return storageContext;
1183
1323
  }
1184
1324
  /**
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.
1325
+ * Shows the commissioning QR code for a given plugin.
1326
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
1327
+ * @param {StorageContext} storageContext - The storage context instance.
1328
+ * @param {NodeStorage} nodeContext - The node storage instance.
1329
+ * @param {string} pluginName - The name of the plugin.
1330
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
1194
1331
  */
1195
1332
  async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1196
1333
  if (!commissioningServer || !storageContext || !pluginName)
@@ -1207,6 +1344,8 @@ export class Matterbridge extends EventEmitter {
1207
1344
  if (pluginName !== 'Matterbridge') {
1208
1345
  const plugin = this.findPlugin(pluginName);
1209
1346
  if (plugin) {
1347
+ plugin.qrPairingCode = qrPairingCode;
1348
+ plugin.manualPairingCode = manualPairingCode;
1210
1349
  plugin.paired = false;
1211
1350
  }
1212
1351
  }
@@ -1528,9 +1667,24 @@ export class Matterbridge extends EventEmitter {
1528
1667
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
1529
1668
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
1530
1669
  // Global node_modules directory
1670
+ /*
1531
1671
  this.globalModulesDirectory = await this.getGlobalNodeModules();
1532
1672
  this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1533
1673
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1674
+ */
1675
+ if (this.nodeContext)
1676
+ this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
1677
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1678
+ this.getGlobalNodeModules()
1679
+ .then(async (globalModulesDirectory) => {
1680
+ this.globalModulesDirectory = globalModulesDirectory;
1681
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1682
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1683
+ await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
1684
+ })
1685
+ .catch((error) => {
1686
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1687
+ });
1534
1688
  // Create the data directory .matterbridge in the home directory
1535
1689
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
1536
1690
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
@@ -1584,12 +1738,28 @@ export class Matterbridge extends EventEmitter {
1584
1738
  this.matterbridgeVersion = packageJson.version;
1585
1739
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
1586
1740
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1741
+ // Matterbridge latest version
1742
+ /*
1587
1743
  this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
1588
1744
  this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
1589
1745
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1590
- if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
1591
- this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
1592
- }
1746
+ */
1747
+ if (this.nodeContext)
1748
+ this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
1749
+ this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1750
+ this.getLatestVersion('matterbridge')
1751
+ .then(async (matterbridgeLatestVersion) => {
1752
+ this.matterbridgeLatestVersion = matterbridgeLatestVersion;
1753
+ this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
1754
+ this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1755
+ await this.nodeContext?.set('matterbridgeLatestVersion', this.matterbridgeLatestVersion);
1756
+ if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
1757
+ this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
1758
+ }
1759
+ })
1760
+ .catch((error) => {
1761
+ this.log.error(`Error getting Matterbridge latest version: ${error}`);
1762
+ });
1593
1763
  // Current working directory
1594
1764
  const currentDir = process.cwd();
1595
1765
  this.log.debug(`Current Working Directory: ${currentDir}`);
@@ -1597,6 +1767,11 @@ export class Matterbridge extends EventEmitter {
1597
1767
  const cmdArgs = process.argv.slice(2).join(' ');
1598
1768
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
1599
1769
  }
1770
+ /**
1771
+ * Retrieves an array of base registered plugins.
1772
+ *
1773
+ * @returns {BaseRegisteredPlugin[]} An array of base registered plugins.
1774
+ */
1600
1775
  getBaseRegisteredPlugins() {
1601
1776
  const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
1602
1777
  path: plugin.path,
@@ -1617,6 +1792,10 @@ export class Matterbridge extends EventEmitter {
1617
1792
  }));
1618
1793
  return baseRegisteredPlugins;
1619
1794
  }
1795
+ /**
1796
+ * Retrieves an array of base registered devices from the registered plugins.
1797
+ * @returns {BaseRegisteredPlugin[]} An array of base registered devices.
1798
+ */
1620
1799
  getBaseRegisteredDevices() {
1621
1800
  const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
1622
1801
  path: plugin.path,
@@ -1648,6 +1827,7 @@ export class Matterbridge extends EventEmitter {
1648
1827
  this.expressApp.get('/api/qr-code', (req, res) => {
1649
1828
  this.log.debug('The frontend sent /api/qr-code');
1650
1829
  if (!this.matterbridgeContext) {
1830
+ this.log.error('/api/qr-code matterbridgeContext not found');
1651
1831
  res.json([]);
1652
1832
  return;
1653
1833
  }
@@ -1751,7 +1931,7 @@ export class Matterbridge extends EventEmitter {
1751
1931
  // Endpoint to receive commands
1752
1932
  this.expressApp.post('/api/command/:command/:param', async (req, res) => {
1753
1933
  const command = req.params.command;
1754
- const param = req.params.param;
1934
+ let param = req.params.param;
1755
1935
  this.log.debug(`The frontend sent /api/command/${command}/${param}`);
1756
1936
  if (!command) {
1757
1937
  res.status(400).json({ error: 'No command provided' });
@@ -1787,9 +1967,55 @@ export class Matterbridge extends EventEmitter {
1787
1967
  if (command === 'update') {
1788
1968
  this.log.warn(`The /api/command/${command} is not yet implemented`);
1789
1969
  }
1790
- // Handle the command addplugin from Header
1970
+ // Handle the command addplugin from Home C:\Users\lligu\GitHub\matterbridge-example-accessory-platform
1791
1971
  if (command === 'addplugin') {
1792
- this.log.warn(`The /api/command/${command}/${param} is not yet implemented`);
1972
+ param = param.replace(/\*/g, '\\');
1973
+ if (this.registeredPlugins.find((plugin) => plugin.name === param)) {
1974
+ this.log.warn(`Plugin ${plg}${param}${wr} already added to matterbridge`);
1975
+ res.json({ message: 'Command received' });
1976
+ return;
1977
+ }
1978
+ const packageJsonPath = await this.resolvePluginName(param);
1979
+ if (!packageJsonPath) {
1980
+ this.log.error(`Error resolving plugin ${plg}${param}${er}`);
1981
+ res.json({ message: 'Command received' });
1982
+ return;
1983
+ }
1984
+ this.log.debug(`Loading plugin from ${plg}${packageJsonPath}${db}`);
1985
+ try {
1986
+ // Load the package.json of the plugin
1987
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
1988
+ const plugin = { path: packageJsonPath, type: '', name: packageJson.name, version: packageJson.version, description: packageJson.description, author: packageJson.author, enabled: true };
1989
+ if (await this.loadPlugin(plugin)) {
1990
+ this.registeredPlugins.push(plugin);
1991
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1992
+ this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge. Restart required.`);
1993
+ }
1994
+ else {
1995
+ this.log.error(`Error adding plugin ${plg}${packageJsonPath}${er}`);
1996
+ }
1997
+ }
1998
+ catch (error) {
1999
+ this.log.error(`Error adding plugin ${plg}${packageJsonPath}${er}`);
2000
+ res.json({ message: 'Command received' });
2001
+ return;
2002
+ }
2003
+ }
2004
+ // Handle the command removeplugin from Home
2005
+ if (command === 'removeplugin') {
2006
+ const index = this.registeredPlugins.findIndex((registeredPlugin) => registeredPlugin.name === param);
2007
+ if (index !== -1) {
2008
+ if (this.registeredPlugins[index].platform) {
2009
+ await this.registeredPlugins[index].platform?.onShutdown('The plugin has been removed.');
2010
+ await this.savePluginConfig(this.registeredPlugins[index]);
2011
+ }
2012
+ this.registeredPlugins.splice(index, 1);
2013
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
2014
+ this.log.info(`Plugin ${plg}${param}${nf} removed from matterbridge`);
2015
+ }
2016
+ else {
2017
+ this.log.warn(`Plugin ${plg}${param}${wr} not registered in matterbridge`);
2018
+ }
1793
2019
  }
1794
2020
  // Handle the command enableplugin from Home
1795
2021
  if (command === 'enableplugin') {