matterbridge 1.5.10 → 1.6.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 (69) hide show
  1. package/CHANGELOG.md +42 -4
  2. package/README-SERVICE.md +1 -1
  3. package/README.md +11 -7
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +3 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cluster/export.js +1 -1
  8. package/dist/cluster/export.js.map +1 -1
  9. package/dist/defaultConfigSchema.d.ts +1 -1
  10. package/dist/defaultConfigSchema.d.ts.map +1 -1
  11. package/dist/defaultConfigSchema.js +10 -2
  12. package/dist/defaultConfigSchema.js.map +1 -1
  13. package/dist/deviceManager.d.ts +1 -1
  14. package/dist/deviceManager.js +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/matterbridge.d.ts +92 -56
  18. package/dist/matterbridge.d.ts.map +1 -1
  19. package/dist/matterbridge.js +290 -155
  20. package/dist/matterbridge.js.map +1 -1
  21. package/dist/matterbridgeAccessoryPlatform.d.ts +1 -1
  22. package/dist/matterbridgeAccessoryPlatform.js +1 -1
  23. package/dist/matterbridgeController.d.ts +1 -1
  24. package/dist/matterbridgeController.js +1 -1
  25. package/dist/matterbridgeDevice.d.ts +137 -1029
  26. package/dist/matterbridgeDevice.d.ts.map +1 -1
  27. package/dist/matterbridgeDevice.js +55 -361
  28. package/dist/matterbridgeDevice.js.map +1 -1
  29. package/dist/matterbridgeDynamicPlatform.d.ts +1 -1
  30. package/dist/matterbridgeDynamicPlatform.js +1 -1
  31. package/dist/matterbridgeEdge.d.ts +90 -0
  32. package/dist/matterbridgeEdge.d.ts.map +1 -0
  33. package/dist/matterbridgeEdge.js +555 -0
  34. package/dist/matterbridgeEdge.js.map +1 -0
  35. package/dist/matterbridgeEndpoint.d.ts +5164 -0
  36. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  37. package/dist/matterbridgeEndpoint.js +2196 -0
  38. package/dist/matterbridgeEndpoint.js.map +1 -0
  39. package/dist/matterbridgePlatform.d.ts +1 -1
  40. package/dist/matterbridgePlatform.js +1 -1
  41. package/dist/matterbridgeTypes.d.ts +5 -0
  42. package/dist/matterbridgeTypes.d.ts.map +1 -1
  43. package/dist/matterbridgeTypes.js.map +1 -1
  44. package/dist/matterbridgeWebsocket.d.ts +49 -0
  45. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  46. package/dist/matterbridgeWebsocket.js +181 -0
  47. package/dist/matterbridgeWebsocket.js.map +1 -0
  48. package/dist/pluginManager.d.ts +135 -2
  49. package/dist/pluginManager.d.ts.map +1 -1
  50. package/dist/pluginManager.js +146 -13
  51. package/dist/pluginManager.js.map +1 -1
  52. package/dist/utils/colorUtils.d.ts +1 -1
  53. package/dist/utils/colorUtils.js +1 -1
  54. package/dist/utils/utils.d.ts +21 -1
  55. package/dist/utils/utils.d.ts.map +1 -1
  56. package/dist/utils/utils.js +57 -1
  57. package/dist/utils/utils.js.map +1 -1
  58. package/frontend/build/asset-manifest.json +3 -3
  59. package/frontend/build/index.html +1 -1
  60. package/frontend/build/static/js/{main.96d6324b.js → main.045d08f7.js} +3 -3
  61. package/frontend/build/static/js/main.045d08f7.js.map +1 -0
  62. package/npm-shrinkwrap.json +816 -6210
  63. package/package.json +2 -78
  64. package/dist/history/export.d.ts +0 -2
  65. package/dist/history/export.d.ts.map +0 -1
  66. package/dist/history/export.js +0 -2
  67. package/dist/history/export.js.map +0 -1
  68. package/frontend/build/static/js/main.96d6324b.js.map +0 -1
  69. /package/frontend/build/static/js/{main.96d6324b.js.LICENSE.txt → main.045d08f7.js.LICENSE.txt} +0 -0
@@ -36,7 +36,7 @@ import { NodeStorageManager } from 'node-persist-manager';
36
36
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, hk, or, idn, BLUE, CYAN, nt } from 'node-ansi-logger';
37
37
  // Matterbridge
38
38
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
39
- import { logInterfaces, wait, waiter, createZip } from './utils/utils.js';
39
+ import { logInterfaces, wait, waiter, createZip, copyDirectory } from './utils/utils.js';
40
40
  import { PluginManager } from './pluginManager.js';
41
41
  import { DeviceManager } from './deviceManager.js';
42
42
  // @project-chip/matter-node.js
@@ -50,6 +50,7 @@ import { StorageBackendDisk, StorageBackendJsonFile, StorageManager } from '@pro
50
50
  import { getParameter, getIntParameter, hasParameter } from '@project-chip/matter-node.js/util';
51
51
  import { CryptoNode } from '@project-chip/matter-node.js/crypto';
52
52
  import { Specification } from '@project-chip/matter-node.js/model';
53
+ import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
53
54
  // Default colors
54
55
  const plg = '\u001B[38;5;33m';
55
56
  const dev = '\u001B[38;5;79m';
@@ -130,13 +131,13 @@ export class Matterbridge extends EventEmitter {
130
131
  initialized = false;
131
132
  execRunningCount = 0;
132
133
  startMatterInterval;
133
- cleanupTimeout1;
134
- cleanupTimeout2;
135
134
  checkUpdateInterval;
136
135
  configureTimeout;
137
136
  reachabilityTimeout;
138
137
  sigintHandler;
139
138
  sigtermHandler;
139
+ exceptionHandler;
140
+ rejectionHandler;
140
141
  // Frontend
141
142
  expressApp;
142
143
  httpServer;
@@ -156,11 +157,16 @@ export class Matterbridge extends EventEmitter {
156
157
  matterAggregator;
157
158
  commissioningServer;
158
159
  commissioningController;
160
+ aggregatorVendorId = VendorId(0xfff1);
161
+ aggregatorProductId = 0x8000;
159
162
  static instance;
160
163
  // We load asyncronously so is private
161
164
  constructor() {
162
165
  super();
166
+ // Bind the handler to the instance
167
+ this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
163
168
  }
169
+ matterbridgeMessageHandler;
164
170
  /** ***********************************************************************************************************************************/
165
171
  /** loadInstance() and cleanup() methods */
166
172
  /** ***********************************************************************************************************************************/
@@ -219,19 +225,54 @@ export class Matterbridge extends EventEmitter {
219
225
  // Set the matterbridge directory
220
226
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
221
227
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
222
- // Initialize nodeStorage and nodeContext
223
- // this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
224
- this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
225
- // this.log.debug('Creating node storage context for matterbridge');
226
- this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
227
- // Check if the storage is corrupted and remove it
228
- // TODO: Check if the storage is corrupted and remove it
229
228
  // Create matterbridge logger
230
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
231
- // Create the file logger for matterbridge (context: matterbridgeFileLog)
232
- if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
233
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
234
- this.matterbridgeInformation.fileLogger = true;
229
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
230
+ // Initialize nodeStorage and nodeContext
231
+ try {
232
+ this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
233
+ this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
234
+ this.log.debug('Creating node storage context for matterbridge');
235
+ this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
236
+ // TODO: Remove this code when node-persist-manager is updated
237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
+ const keys = (await this.nodeStorage?.storage.keys());
239
+ for (const key of keys) {
240
+ this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
+ await this.nodeStorage?.storage.get(key);
243
+ }
244
+ const storages = await this.nodeStorage.getStorageNames();
245
+ for (const storage of storages) {
246
+ this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
247
+ const nodeContext = await this.nodeStorage?.createStorage(storage);
248
+ // TODO: Remove this code when node-persist-manager is updated
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ const keys = (await nodeContext?.storage.keys());
251
+ keys.forEach(async (key) => {
252
+ this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
253
+ await nodeContext?.get(key);
254
+ });
255
+ }
256
+ // Creating a backup of the node storage since it is not corrupted
257
+ this.log.debug('Creating node storage backup...');
258
+ await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
259
+ this.log.debug('Created node storage backup');
260
+ }
261
+ catch (error) {
262
+ // Restoring the backup of the node storage since it is corrupted
263
+ this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
264
+ if (hasParameter('norestore')) {
265
+ this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
266
+ await this.cleanup('Fatal error creating node storage manager and context for matterbridge');
267
+ return;
268
+ }
269
+ this.log.notice(`The matterbridge storage is corrupted. Restoring it with backup...`);
270
+ await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'), path.join(this.matterbridgeDirectory, this.nodeStorageName));
271
+ this.log.notice(`The matterbridge storage has been restored with backup`);
272
+ }
273
+ if (!this.nodeStorage || !this.nodeContext) {
274
+ this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
275
+ throw new Error('Fatal error creating node storage manager and context for matterbridge');
235
276
  }
236
277
  // Set matterbridge logger level (context: matterbridgeLogLevel)
237
278
  if (hasParameter('logger')) {
@@ -263,6 +304,11 @@ export class Matterbridge extends EventEmitter {
263
304
  this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
264
305
  }
265
306
  MatterbridgeDevice.logLevel = this.log.logLevel;
307
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
308
+ if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
309
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
310
+ this.matterbridgeInformation.fileLogger = true;
311
+ }
266
312
  this.log.notice('Matterbridge is starting...');
267
313
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
268
314
  // Set matter.js logger level, format and logger (context: matterLogLevel)
@@ -300,7 +346,7 @@ export class Matterbridge extends EventEmitter {
300
346
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
301
347
  this.matterbridgeInformation.matterFileLogger = true;
302
348
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
303
- defaultLogLevel: Level.DEBUG,
349
+ defaultLogLevel: Logger.defaultLogLevel,
304
350
  logFormat: Format.PLAIN,
305
351
  });
306
352
  }
@@ -345,7 +391,7 @@ export class Matterbridge extends EventEmitter {
345
391
  // We don't do this when the add parameter is set because we shut down the process after adding the plugin
346
392
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
347
393
  try {
348
- await this.spawnCommand('npm', ['install', '-g', '--omit=dev', plugin.name]);
394
+ await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
349
395
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
350
396
  plugin.error = false;
351
397
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -381,8 +427,8 @@ export class Matterbridge extends EventEmitter {
381
427
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
382
428
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
383
429
  }
384
- // Register SIGINT SIGTERM signal handlers
385
- this.registerSignalHandlers();
430
+ // Register process handlers
431
+ this.registerProcessHandlers();
386
432
  // Parse command line
387
433
  await this.parseCommandLine();
388
434
  this.initialized = true;
@@ -413,6 +459,7 @@ export class Matterbridge extends EventEmitter {
413
459
  - logstorage: log the node storage
414
460
  - sudo: force the use of sudo to install or update packages if the internal logic fails
415
461
  - nosudo: force not to use sudo to install or update packages if the internal logic fails
462
+ - norestore: force not to automatically restore the matterbridge node storage and the matter storage from backup if it is corrupted
416
463
  - ssl: enable SSL for the frontend and WebSockerServer (certificates in .matterbridge/certs directory cert.pem, key.pem and ca.pem (optional))
417
464
  - add [plugin path]: register the plugin from the given absolute or relative path
418
465
  - add [plugin name]: register the globally installed plugin with the given name
@@ -520,9 +567,13 @@ export class Matterbridge extends EventEmitter {
520
567
  return;
521
568
  }
522
569
  // Start the matter storage and create the matterbridge context
523
- await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
524
- this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
525
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
570
+ try {
571
+ await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
572
+ }
573
+ catch (error) {
574
+ this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
575
+ throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
576
+ }
526
577
  if (hasParameter('reset') && getParameter('reset') === undefined) {
527
578
  this.log.info('Resetting Matterbridge commissioning information...');
528
579
  await this.matterbridgeContext?.clearAll();
@@ -560,12 +611,13 @@ export class Matterbridge extends EventEmitter {
560
611
  this.getPluginLatestVersion(plugin);
561
612
  }
562
613
  }, 60 * 60 * 1000);
614
+ // Start the matterbridge in mode test
563
615
  if (hasParameter('test')) {
564
616
  this.bridgeMode = 'bridge';
565
617
  MatterbridgeDevice.bridgeMode = 'bridge';
566
- await this.startTest();
567
618
  return;
568
619
  }
620
+ // Start the matterbridge in mode controller
569
621
  if (hasParameter('controller')) {
570
622
  this.bridgeMode = 'controller';
571
623
  await this.startController();
@@ -576,96 +628,81 @@ export class Matterbridge extends EventEmitter {
576
628
  this.log.info('Setting default matterbridge start mode to bridge');
577
629
  await this.nodeContext?.set('bridgeMode', 'bridge');
578
630
  }
631
+ // Start matterbridge in bridge mode
579
632
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
580
633
  this.bridgeMode = 'bridge';
581
634
  MatterbridgeDevice.bridgeMode = 'bridge';
582
- if (!this.storageManager)
583
- throw new Error('No storage manager initialized');
584
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
585
- this.matterServer = this.createMatterServer(this.storageManager);
586
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
587
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
588
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
589
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
590
- this.log.debug('Adding matterbridge aggregator to commissioning server');
591
- this.commissioningServer.addDevice(this.matterAggregator);
592
- this.log.debug('Adding matterbridge commissioning server to matter server');
593
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
594
- for (const plugin of this.plugins) {
595
- plugin.configJson = await this.plugins.loadConfig(plugin);
596
- plugin.schemaJson = await this.plugins.loadSchema(plugin);
597
- // Check if the plugin is available
598
- if (!(await this.plugins.resolve(plugin.path))) {
599
- this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
600
- plugin.enabled = false;
601
- plugin.error = true;
602
- continue;
603
- }
604
- // Check if the plugin has a new version
605
- this.getPluginLatestVersion(plugin); // No await do it asyncronously
606
- if (!plugin.enabled) {
607
- this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
608
- continue;
609
- }
610
- plugin.error = false;
611
- plugin.locked = false;
612
- plugin.loaded = false;
613
- plugin.started = false;
614
- plugin.configured = false;
615
- plugin.connected = undefined;
616
- plugin.registeredDevices = undefined;
617
- plugin.addedDevices = undefined;
618
- plugin.qrPairingCode = undefined;
619
- plugin.manualPairingCode = undefined;
620
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
621
- }
635
+ this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
622
636
  await this.startBridge();
623
637
  return;
624
638
  }
639
+ // Start matterbridge in childbridge mode
625
640
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
626
641
  this.bridgeMode = 'childbridge';
627
642
  MatterbridgeDevice.bridgeMode = 'childbridge';
628
- if (!this.storageManager)
629
- throw new Error('No storage manager initialized');
630
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
631
- this.matterServer = this.createMatterServer(this.storageManager);
632
- for (const plugin of this.plugins) {
633
- plugin.configJson = await this.plugins.loadConfig(plugin);
634
- plugin.schemaJson = await this.plugins.loadSchema(plugin);
635
- // Check if the plugin is available
636
- if (!(await this.plugins.resolve(plugin.path))) {
637
- this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
638
- plugin.enabled = false;
639
- plugin.error = true;
640
- continue;
641
- }
642
- // Check if the plugin has a new version
643
- this.getPluginLatestVersion(plugin); // No await do it asyncronously
644
- if (!plugin.enabled) {
645
- this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
646
- continue;
647
- }
648
- plugin.error = false;
649
- plugin.locked = false;
650
- plugin.loaded = false;
651
- plugin.started = false;
652
- plugin.configured = false;
653
- plugin.connected = false;
654
- plugin.registeredDevices = undefined;
655
- plugin.addedDevices = undefined;
656
- plugin.qrPairingCode = undefined;
657
- plugin.manualPairingCode = undefined;
658
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
659
- }
643
+ this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
660
644
  await this.startChildbridge();
661
645
  return;
662
646
  }
663
647
  }
664
648
  /**
665
- * Registers the signal handlers for SIGINT and SIGTERM.
649
+ * Asynchronously loads and starts the registered plugins.
650
+ *
651
+ * This method is responsible for initializing and staarting all enabled plugins.
652
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
653
+ *
654
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
655
+ */
656
+ async startPlugins() {
657
+ // Check, load and start the plugins
658
+ for (const plugin of this.plugins) {
659
+ plugin.configJson = await this.plugins.loadConfig(plugin);
660
+ plugin.schemaJson = await this.plugins.loadSchema(plugin);
661
+ // Check if the plugin is available
662
+ if (!(await this.plugins.resolve(plugin.path))) {
663
+ this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
664
+ plugin.enabled = false;
665
+ plugin.error = true;
666
+ continue;
667
+ }
668
+ // Check if the plugin has a new version
669
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
670
+ if (!plugin.enabled) {
671
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
672
+ continue;
673
+ }
674
+ plugin.error = false;
675
+ plugin.locked = false;
676
+ plugin.loaded = false;
677
+ plugin.started = false;
678
+ plugin.configured = false;
679
+ plugin.connected = undefined;
680
+ plugin.registeredDevices = undefined;
681
+ plugin.addedDevices = undefined;
682
+ plugin.qrPairingCode = undefined;
683
+ plugin.manualPairingCode = undefined;
684
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
685
+ }
686
+ this.wssSendRefreshRequired();
687
+ }
688
+ /**
689
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
666
690
  * When either of these signals are received, the cleanup method is called with an appropriate message.
667
691
  */
668
- registerSignalHandlers() {
692
+ registerProcessHandlers() {
693
+ this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
694
+ process.removeAllListeners('uncaughtException');
695
+ process.removeAllListeners('unhandledRejection');
696
+ this.exceptionHandler = async (error) => {
697
+ this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
698
+ await this.cleanup('Unhandled Exception detected, cleaning up...');
699
+ };
700
+ process.on('uncaughtException', this.exceptionHandler);
701
+ this.rejectionHandler = async (reason, promise) => {
702
+ this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
703
+ await this.cleanup('Unhandled Rejection detected, cleaning up...');
704
+ };
705
+ process.on('unhandledRejection', this.rejectionHandler);
669
706
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
670
707
  this.sigintHandler = async () => {
671
708
  await this.cleanup('SIGINT received, cleaning up...');
@@ -677,9 +714,16 @@ export class Matterbridge extends EventEmitter {
677
714
  process.on('SIGTERM', this.sigtermHandler);
678
715
  }
679
716
  /**
680
- * Deregisters the SIGINT and SIGTERM signal handlers.
717
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
681
718
  */
682
- deregisterSignalHandlers() {
719
+ deregisterProcesslHandlers() {
720
+ this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
721
+ if (this.exceptionHandler)
722
+ process.off('uncaughtException', this.exceptionHandler);
723
+ this.exceptionHandler = undefined;
724
+ if (this.rejectionHandler)
725
+ process.off('unhandledRejection', this.rejectionHandler);
726
+ this.rejectionHandler = undefined;
683
727
  this.log.debug(`Deregistering SIGINT and SIGTERM signal handlers...`);
684
728
  if (this.sigintHandler)
685
729
  process.off('SIGINT', this.sigintHandler);
@@ -1080,8 +1124,8 @@ export class Matterbridge extends EventEmitter {
1080
1124
  if (this.initialized && !this.hasCleanupStarted) {
1081
1125
  this.hasCleanupStarted = true;
1082
1126
  this.log.info(message);
1083
- // Deregisters the SIGINT and SIGTERM signal handlers
1084
- this.deregisterSignalHandlers();
1127
+ // Deregisters the process handlers
1128
+ this.deregisterProcesslHandlers();
1085
1129
  // Clear the start matter interval
1086
1130
  if (this.startMatterInterval) {
1087
1131
  clearInterval(this.startMatterInterval);
@@ -1155,7 +1199,6 @@ export class Matterbridge extends EventEmitter {
1155
1199
  });
1156
1200
  this.webSocketServer = undefined;
1157
1201
  }
1158
- // this.cleanupTimeout1 = setTimeout(async () => {
1159
1202
  // Closing matter
1160
1203
  await this.stopMatterServer();
1161
1204
  // Closing matter storage
@@ -1201,9 +1244,6 @@ export class Matterbridge extends EventEmitter {
1201
1244
  }
1202
1245
  this.plugins.clear();
1203
1246
  this.devices.clear();
1204
- // this.registeredDevices = [];
1205
- // this.log.info('Waiting for matter to deliver last messages...');
1206
- // this.cleanupTimeout2 = setTimeout(async () => {
1207
1247
  if (restart) {
1208
1248
  if (message === 'updating...') {
1209
1249
  this.log.info('Cleanup completed. Updating...');
@@ -1238,8 +1278,6 @@ export class Matterbridge extends EventEmitter {
1238
1278
  }
1239
1279
  this.hasCleanupStarted = false;
1240
1280
  this.initialized = false;
1241
- // }, 2 * 1000);
1242
- // }, 3 * 1000);
1243
1281
  }
1244
1282
  }
1245
1283
  /**
@@ -1249,17 +1287,17 @@ export class Matterbridge extends EventEmitter {
1249
1287
  * @returns {Promise<void>} - A promise that resolves when the device is added.
1250
1288
  */
1251
1289
  async addBridgedDevice(pluginName, device) {
1252
- this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1290
+ this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1253
1291
  // Check if the plugin is registered
1254
1292
  const plugin = this.plugins.get(pluginName);
1255
1293
  if (!plugin) {
1256
- this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1294
+ this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1257
1295
  return;
1258
1296
  }
1259
1297
  // Register and add the device to matterbridge aggregator in bridge mode
1260
1298
  if (this.bridgeMode === 'bridge') {
1261
1299
  if (!this.matterAggregator) {
1262
- this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1300
+ this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1263
1301
  return;
1264
1302
  }
1265
1303
  this.matterAggregator.addBridgedDevice(device);
@@ -1314,17 +1352,17 @@ export class Matterbridge extends EventEmitter {
1314
1352
  * @returns A Promise that resolves when the device is successfully removed.
1315
1353
  */
1316
1354
  async removeBridgedDevice(pluginName, device) {
1317
- this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1355
+ this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1318
1356
  // Check if the plugin is registered
1319
1357
  const plugin = this.plugins.get(pluginName);
1320
1358
  if (!plugin) {
1321
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1359
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1322
1360
  return;
1323
1361
  }
1324
1362
  // Remove the device from matterbridge aggregator in bridge mode
1325
1363
  if (this.bridgeMode === 'bridge') {
1326
1364
  if (!this.matterAggregator) {
1327
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1365
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1328
1366
  return;
1329
1367
  }
1330
1368
  if (device.number !== undefined) {
@@ -1334,7 +1372,7 @@ export class Matterbridge extends EventEmitter {
1334
1372
  // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1335
1373
  // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1336
1374
  this.matterAggregator?.removeBridgedDevice(device);
1337
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1375
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1338
1376
  if (plugin.registeredDevices !== undefined)
1339
1377
  plugin.registeredDevices--;
1340
1378
  if (plugin.addedDevices !== undefined)
@@ -1344,13 +1382,13 @@ export class Matterbridge extends EventEmitter {
1344
1382
  if (this.bridgeMode === 'childbridge') {
1345
1383
  if (plugin.type === 'AccessoryPlatform') {
1346
1384
  if (!plugin.commissioningServer) {
1347
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1385
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1348
1386
  return;
1349
1387
  }
1350
1388
  }
1351
1389
  else if (plugin.type === 'DynamicPlatform') {
1352
1390
  if (!plugin.aggregator) {
1353
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1391
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1354
1392
  return;
1355
1393
  }
1356
1394
  if (device.number !== undefined) {
@@ -1359,7 +1397,7 @@ export class Matterbridge extends EventEmitter {
1359
1397
  }
1360
1398
  plugin.aggregator.removeBridgedDevice(device);
1361
1399
  }
1362
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1400
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1363
1401
  if (plugin.registeredDevices !== undefined)
1364
1402
  plugin.registeredDevices--;
1365
1403
  if (plugin.addedDevices !== undefined)
@@ -1388,18 +1426,28 @@ export class Matterbridge extends EventEmitter {
1388
1426
  }
1389
1427
  });
1390
1428
  }
1391
- async startTest() {
1392
- // Start the Matterbridge test
1393
- }
1394
1429
  /**
1395
1430
  * Starts the Matterbridge in bridge mode.
1396
1431
  * @private
1397
1432
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1398
1433
  */
1399
1434
  async startBridge() {
1400
- // Plugins are loaded and started by loadPlugin on startup and plugin.loaded and plugin.started are set to true
1401
1435
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1402
- this.log.debug('Starting startMatterInterval in bridge mode');
1436
+ if (!this.storageManager)
1437
+ throw new Error('No storage manager initialized');
1438
+ if (!this.matterbridgeContext)
1439
+ throw new Error('No storage context initialized');
1440
+ this.matterServer = this.createMatterServer(this.storageManager);
1441
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1442
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1443
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1444
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
1445
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
1446
+ this.commissioningServer.addDevice(this.matterAggregator);
1447
+ this.log.debug('Adding matterbridge commissioning server to matter server');
1448
+ await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1449
+ await this.startPlugins();
1450
+ this.log.debug('Starting start matter interval in bridge mode');
1403
1451
  let failCount = 0;
1404
1452
  this.startMatterInterval = setInterval(async () => {
1405
1453
  for (const plugin of this.plugins) {
@@ -1428,8 +1476,11 @@ export class Matterbridge extends EventEmitter {
1428
1476
  clearInterval(this.startMatterInterval);
1429
1477
  this.startMatterInterval = undefined;
1430
1478
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1479
+ // Start the Matter server
1431
1480
  await this.startMatterServer();
1432
1481
  this.log.notice('Matter server started');
1482
+ // Show the QR code for commissioning or log the already commissioned message
1483
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1433
1484
  // Configure the plugins
1434
1485
  this.configureTimeout = setTimeout(async () => {
1435
1486
  for (const plugin of this.plugins) {
@@ -1443,9 +1494,8 @@ export class Matterbridge extends EventEmitter {
1443
1494
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1444
1495
  }
1445
1496
  }
1497
+ this.wssSendRefreshRequired();
1446
1498
  }, 30 * 1000);
1447
- // Show the QR code for commissioning or log the already commissioned message
1448
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1449
1499
  // Setting reachability to true
1450
1500
  this.reachabilityTimeout = setTimeout(() => {
1451
1501
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
@@ -1462,9 +1512,12 @@ export class Matterbridge extends EventEmitter {
1462
1512
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1463
1513
  */
1464
1514
  async startChildbridge() {
1465
- // Plugins are loaded and started by loadPlugin on startup and plugin.loaded and plugin.started are set to true
1466
- // addDevice and addBridgedDeevice create the commissionig servers and add the devices to the the commissioning server or to the aggregator
1515
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1467
1516
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1517
+ if (!this.storageManager)
1518
+ throw new Error('No storage manager initialized');
1519
+ this.matterServer = this.createMatterServer(this.storageManager);
1520
+ await this.startPlugins();
1468
1521
  this.log.debug('Starting start matter interval in childbridge mode...');
1469
1522
  let failCount = 0;
1470
1523
  this.startMatterInterval = setInterval(async () => {
@@ -1476,7 +1529,7 @@ export class Matterbridge extends EventEmitter {
1476
1529
  if (plugin.error) {
1477
1530
  clearInterval(this.startMatterInterval);
1478
1531
  this.startMatterInterval = undefined;
1479
- this.log.debug('Cleared startMatterInterval interval for Matterbridge for plugin in error state');
1532
+ this.log.debug('Cleared startMatterInterval interval for a plugin in error state');
1480
1533
  this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1481
1534
  this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1482
1535
  this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
@@ -1498,6 +1551,7 @@ export class Matterbridge extends EventEmitter {
1498
1551
  clearInterval(this.startMatterInterval);
1499
1552
  this.startMatterInterval = undefined;
1500
1553
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1554
+ // Start the Matter server
1501
1555
  await this.startMatterServer();
1502
1556
  this.log.notice('Matter server started');
1503
1557
  // Configure the plugins
@@ -1513,6 +1567,7 @@ export class Matterbridge extends EventEmitter {
1513
1567
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1514
1568
  }
1515
1569
  }
1570
+ this.wssSendRefreshRequired();
1516
1571
  }, 30 * 1000);
1517
1572
  for (const plugin of this.plugins) {
1518
1573
  if (!plugin.enabled || plugin.error)
@@ -1733,7 +1788,7 @@ export class Matterbridge extends EventEmitter {
1733
1788
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1734
1789
  */
1735
1790
  async startMatterStorage(storageType, storageName) {
1736
- this.log.debug(`Starting ${storageType} storage ${CYAN}${storageName}${db}`);
1791
+ this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1737
1792
  if (storageType === 'disk') {
1738
1793
  const storageDisk = new StorageBackendDisk(storageName);
1739
1794
  this.storageManager = new StorageManager(storageDisk);
@@ -1745,23 +1800,37 @@ export class Matterbridge extends EventEmitter {
1745
1800
  this.storageManager = new StorageManager(storageJson);
1746
1801
  }
1747
1802
  else {
1748
- this.log.error(`Unsupported storage type ${storageType}`);
1749
- await this.cleanup('Unsupported storage type');
1803
+ this.log.error(`Unsupported matter storage type ${storageType}`);
1804
+ await this.cleanup('Unsupported matter storage type');
1750
1805
  return;
1751
1806
  }
1752
1807
  try {
1753
1808
  await this.storageManager.initialize();
1754
- this.log.debug('Storage initialized');
1809
+ this.log.debug('Matter storage initialized');
1755
1810
  if (storageType === 'json') {
1756
- await this.backupJsonMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1811
+ await this.backupMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1757
1812
  }
1758
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1759
1813
  }
1760
1814
  catch (error) {
1761
- this.log.error(`Storage initialize() error! The file .matterbridge/${storageName} may be corrupted.`);
1762
- this.log.error(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
1763
- await this.cleanup('Storage initialize() error!');
1815
+ this.log.error(`Matter storage initialize error! The file .matterbridge/${storageName} may be corrupted: ${error instanceof Error ? error.message : error}`);
1816
+ if (hasParameter('norestore')) {
1817
+ this.log.fatal(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
1818
+ await this.cleanup('Matter storage initialize error and -norestore parameter found!');
1819
+ return;
1820
+ }
1821
+ await this.restoreMatterStorage(storageName.replace('.json', '') + '.backup.json', storageName);
1822
+ try {
1823
+ await this.storageManager.initialize();
1824
+ this.log.notice('Matter storage initialized from the backup file');
1825
+ }
1826
+ catch (error) {
1827
+ this.log.error(`Matter storage initialize error! The backup file for .matterbridge/${storageName} may be corrupted too:`, error instanceof Error ? error.message : error);
1828
+ await this.cleanup('Matter storage initialize error from backup!');
1829
+ return;
1830
+ }
1764
1831
  }
1832
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1833
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1765
1834
  }
1766
1835
  /**
1767
1836
  * Makes a backup copy of the specified matter JSON storage file.
@@ -1769,7 +1838,7 @@ export class Matterbridge extends EventEmitter {
1769
1838
  * @param storageName - The name of the JSON storage file to be backed up.
1770
1839
  * @param backupName - The name of the backup file to be created.
1771
1840
  */
1772
- async backupJsonMatterStorage(storageName, backupName) {
1841
+ async backupMatterStorage(storageName, backupName) {
1773
1842
  try {
1774
1843
  this.log.debug(`Making backup copy of ${storageName}`);
1775
1844
  await fs.copyFile(storageName, backupName);
@@ -1778,7 +1847,7 @@ export class Matterbridge extends EventEmitter {
1778
1847
  catch (err) {
1779
1848
  if (err instanceof Error && 'code' in err) {
1780
1849
  if (err.code === 'ENOENT') {
1781
- this.log.info(`No existing file to back up for ${storageName}. This is expected on the first run.`);
1850
+ this.log.debug(`No existing file to back up for ${storageName}. This is expected on the first run.`);
1782
1851
  }
1783
1852
  else {
1784
1853
  this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
@@ -1789,6 +1858,32 @@ export class Matterbridge extends EventEmitter {
1789
1858
  }
1790
1859
  }
1791
1860
  }
1861
+ /**
1862
+ * Restore the specified matter JSON storage file.
1863
+ *
1864
+ * @param backupName - The name of the backup file to restore from.
1865
+ * @param storageName - The name of the JSON storage file to restored.
1866
+ */
1867
+ async restoreMatterStorage(backupName, storageName) {
1868
+ try {
1869
+ this.log.notice(`Restoring the backup copy of ${storageName}`);
1870
+ await fs.copyFile(backupName, storageName);
1871
+ this.log.notice(`Successfully restored ${backupName} to ${storageName}`);
1872
+ }
1873
+ catch (err) {
1874
+ if (err instanceof Error && 'code' in err) {
1875
+ if (err.code === 'ENOENT') {
1876
+ this.log.info(`No existing file to restore: ${backupName}.`);
1877
+ }
1878
+ else {
1879
+ this.log.error(`Error restoring ${backupName}: ${err.message}`);
1880
+ }
1881
+ }
1882
+ else {
1883
+ this.log.error(`An unexpected error occurred during the restore of ${backupName}: ${String(err)}`);
1884
+ }
1885
+ }
1886
+ }
1792
1887
  /**
1793
1888
  * Stops the matter storage.
1794
1889
  * @returns {Promise<void>} A promise that resolves when the storage is stopped.
@@ -2010,6 +2105,7 @@ export class Matterbridge extends EventEmitter {
2010
2105
  }
2011
2106
  }
2012
2107
  }
2108
+ this.wssSendRefreshRequired();
2013
2109
  },
2014
2110
  commissioningChangedCallback: async (fabricIndex) => {
2015
2111
  const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
@@ -2052,6 +2148,7 @@ export class Matterbridge extends EventEmitter {
2052
2148
  }
2053
2149
  }
2054
2150
  }
2151
+ this.wssSendRefreshRequired();
2055
2152
  },
2056
2153
  });
2057
2154
  if (this.passcode !== undefined)
@@ -2211,6 +2308,7 @@ export class Matterbridge extends EventEmitter {
2211
2308
  }
2212
2309
  }
2213
2310
  }
2311
+ this.wssSendRefreshRequired();
2214
2312
  }
2215
2313
  /**
2216
2314
  * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
@@ -2333,6 +2431,9 @@ export class Matterbridge extends EventEmitter {
2333
2431
  case 4701:
2334
2432
  vendorName = '(Tuya)';
2335
2433
  break;
2434
+ case 4718:
2435
+ vendorName = '(Xiaomi)';
2436
+ break;
2336
2437
  case 4742:
2337
2438
  vendorName = '(eWeLink)';
2338
2439
  break;
@@ -2384,7 +2485,7 @@ export class Matterbridge extends EventEmitter {
2384
2485
  * Spawns a child process with the given command and arguments.
2385
2486
  * @param {string} command - The command to execute.
2386
2487
  * @param {string[]} args - The arguments to pass to the command (default: []).
2387
- * @returns {Promise<void>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2488
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2388
2489
  */
2389
2490
  async spawnCommand(command, args = []) {
2390
2491
  /*
@@ -2429,7 +2530,7 @@ export class Matterbridge extends EventEmitter {
2429
2530
  if (cmdLine.startsWith('npm install -g'))
2430
2531
  this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
2431
2532
  this.log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
2432
- resolve();
2533
+ resolve(true);
2433
2534
  }
2434
2535
  else {
2435
2536
  this.log.error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
@@ -2440,7 +2541,7 @@ export class Matterbridge extends EventEmitter {
2440
2541
  this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
2441
2542
  if (code === 0) {
2442
2543
  this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
2443
- resolve();
2544
+ resolve(true);
2444
2545
  }
2445
2546
  else {
2446
2547
  this.log.error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
@@ -2449,7 +2550,7 @@ export class Matterbridge extends EventEmitter {
2449
2550
  });
2450
2551
  childProcess.on('disconnect', () => {
2451
2552
  this.log.debug(`Child process "${cmdLine}" has been disconnected from the parent`);
2452
- resolve();
2553
+ resolve(true);
2453
2554
  });
2454
2555
  if (childProcess.stdout) {
2455
2556
  childProcess.stdout.on('data', (data) => {
@@ -2508,7 +2609,33 @@ export class Matterbridge extends EventEmitter {
2508
2609
  // Send the message to all connected clients
2509
2610
  this.webSocketServer?.clients.forEach((client) => {
2510
2611
  if (client.readyState === WebSocket.OPEN) {
2511
- client.send(JSON.stringify({ level, time, name, message }));
2612
+ client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2613
+ }
2614
+ });
2615
+ }
2616
+ /**
2617
+ * Sends a need to refresh WebSocket message to all connected clients.
2618
+ *
2619
+ */
2620
+ wssSendRefreshRequired() {
2621
+ this.matterbridgeInformation.refreshRequired = true;
2622
+ // Send the message to all connected clients
2623
+ this.webSocketServer?.clients.forEach((client) => {
2624
+ if (client.readyState === WebSocket.OPEN) {
2625
+ client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2626
+ }
2627
+ });
2628
+ }
2629
+ /**
2630
+ * Sends a need to restart WebSocket message to all connected clients.
2631
+ *
2632
+ */
2633
+ wssSendRestartRequired() {
2634
+ this.matterbridgeInformation.restartRequired = true;
2635
+ // Send the message to all connected clients
2636
+ this.webSocketServer?.clients.forEach((client) => {
2637
+ if (client.readyState === WebSocket.OPEN) {
2638
+ client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2512
2639
  }
2513
2640
  });
2514
2641
  }
@@ -2627,7 +2754,7 @@ export class Matterbridge extends EventEmitter {
2627
2754
  }
2628
2755
  if (initializeError)
2629
2756
  return;
2630
- // Createe a WebSocket server and attach it to the http server
2757
+ // Createe a WebSocket server and attach it to the http or https server
2631
2758
  const wssPort = port;
2632
2759
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2633
2760
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
@@ -2637,6 +2764,14 @@ export class Matterbridge extends EventEmitter {
2637
2764
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2638
2765
  ws.on('message', (message) => {
2639
2766
  this.log.debug(`WebSocket client message: ${message}`);
2767
+ this.matterbridgeMessageHandler(ws, message);
2768
+ });
2769
+ ws.on('ping', () => {
2770
+ this.log.debug('WebSocket client ping');
2771
+ ws.pong();
2772
+ });
2773
+ ws.on('pong', () => {
2774
+ this.log.debug('WebSocket client pong');
2640
2775
  });
2641
2776
  ws.on('close', () => {
2642
2777
  this.log.info('WebSocket client disconnected');
@@ -2699,12 +2834,8 @@ export class Matterbridge extends EventEmitter {
2699
2834
  this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
2700
2835
  this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
2701
2836
  this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
2702
- // this.matterbridgeInformation.matterbridgeSessionInformations = this.matterbridgeSessionInformations;
2703
2837
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2704
- // console.log('this.matterbridgeSessionInformations:', this.matterbridgeSessionInformations);
2705
- if (this.profile)
2706
- this.matterbridgeInformation.profile = this.profile;
2707
- // const response = { wssHost, ssl: hasParameter('ssl'), qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2838
+ this.matterbridgeInformation.profile = this.profile;
2708
2839
  const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2709
2840
  // this.log.debug('Response:', debugStringify(response));
2710
2841
  res.json(response);
@@ -2785,7 +2916,7 @@ export class Matterbridge extends EventEmitter {
2785
2916
  });
2786
2917
  });
2787
2918
  device.getChildEndpoints().forEach((childEndpoint) => {
2788
- const name = device.getChildEndpointName(childEndpoint);
2919
+ const name = childEndpoint.uniqueStorageKey;
2789
2920
  const clusterServers = childEndpoint.getAllClusterServers();
2790
2921
  clusterServers.forEach((clusterServer) => {
2791
2922
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
@@ -2819,7 +2950,7 @@ export class Matterbridge extends EventEmitter {
2819
2950
  });
2820
2951
  res.json(data);
2821
2952
  });
2822
- // Endpoint to send the log
2953
+ // Endpoint to view the log
2823
2954
  this.expressApp.get('/api/view-log', async (req, res) => {
2824
2955
  this.log.debug('The frontend sent /api/log');
2825
2956
  try {
@@ -2941,7 +3072,7 @@ export class Matterbridge extends EventEmitter {
2941
3072
  // Handle the command setbridgemode from Settings
2942
3073
  if (command === 'setbridgemode') {
2943
3074
  this.log.debug(`setbridgemode: ${param}`);
2944
- this.matterbridgeInformation.restartRequired = true;
3075
+ this.wssSendRestartRequired();
2945
3076
  await this.nodeContext?.set('bridgeMode', param);
2946
3077
  res.json({ message: 'Command received' });
2947
3078
  return;
@@ -3121,7 +3252,7 @@ export class Matterbridge extends EventEmitter {
3121
3252
  this.log.error('Error updating matterbridge');
3122
3253
  }
3123
3254
  await this.updateProcess();
3124
- this.matterbridgeInformation.restartRequired = true;
3255
+ this.wssSendRestartRequired();
3125
3256
  res.json({ message: 'Command received' });
3126
3257
  return;
3127
3258
  }
@@ -3139,7 +3270,7 @@ export class Matterbridge extends EventEmitter {
3139
3270
  return;
3140
3271
  this.plugins.saveConfigFromJson(plugin, req.body);
3141
3272
  }
3142
- this.matterbridgeInformation.restartRequired = true;
3273
+ this.wssSendRestartRequired();
3143
3274
  res.json({ message: 'Command received' });
3144
3275
  return;
3145
3276
  }
@@ -3155,7 +3286,7 @@ export class Matterbridge extends EventEmitter {
3155
3286
  catch (error) {
3156
3287
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
3157
3288
  }
3158
- this.matterbridgeInformation.restartRequired = true;
3289
+ this.wssSendRestartRequired();
3159
3290
  // Also add the plugin to matterbridge so no return!
3160
3291
  // res.json({ message: 'Command received' });
3161
3292
  // return;
@@ -3168,6 +3299,7 @@ export class Matterbridge extends EventEmitter {
3168
3299
  this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
3169
3300
  }
3170
3301
  res.json({ message: 'Command received' });
3302
+ this.wssSendRefreshRequired();
3171
3303
  return;
3172
3304
  }
3173
3305
  // Handle the command removeplugin from Home
@@ -3181,6 +3313,7 @@ export class Matterbridge extends EventEmitter {
3181
3313
  await this.plugins.remove(param);
3182
3314
  }
3183
3315
  res.json({ message: 'Command received' });
3316
+ this.wssSendRefreshRequired();
3184
3317
  return;
3185
3318
  }
3186
3319
  // Handle the command enableplugin from Home
@@ -3205,6 +3338,7 @@ export class Matterbridge extends EventEmitter {
3205
3338
  }
3206
3339
  }
3207
3340
  res.json({ message: 'Command received' });
3341
+ this.wssSendRefreshRequired();
3208
3342
  return;
3209
3343
  }
3210
3344
  // Handle the command disableplugin from Home
@@ -3220,6 +3354,7 @@ export class Matterbridge extends EventEmitter {
3220
3354
  }
3221
3355
  }
3222
3356
  res.json({ message: 'Command received' });
3357
+ this.wssSendRefreshRequired();
3223
3358
  return;
3224
3359
  }
3225
3360
  });