matterbridge 1.5.9 → 1.6.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 (51) hide show
  1. package/CHANGELOG.md +36 -3
  2. package/README-DEV.md +10 -6
  3. package/README-PODMAN.md +2 -0
  4. package/README-SERVICE.md +44 -6
  5. package/README.md +53 -7
  6. package/dist/cluster/export.js +1 -1
  7. package/dist/cluster/export.js.map +1 -1
  8. package/dist/defaultConfigSchema.d.ts.map +1 -1
  9. package/dist/defaultConfigSchema.js +9 -1
  10. package/dist/defaultConfigSchema.js.map +1 -1
  11. package/dist/matterbridge.d.ts +72 -48
  12. package/dist/matterbridge.d.ts.map +1 -1
  13. package/dist/matterbridge.js +174 -173
  14. package/dist/matterbridge.js.map +1 -1
  15. package/dist/matterbridgeDevice.d.ts +140 -1042
  16. package/dist/matterbridgeDevice.d.ts.map +1 -1
  17. package/dist/matterbridgeDevice.js +57 -363
  18. package/dist/matterbridgeDevice.js.map +1 -1
  19. package/dist/matterbridgeEdge.d.ts +90 -0
  20. package/dist/matterbridgeEdge.d.ts.map +1 -0
  21. package/dist/matterbridgeEdge.js +555 -0
  22. package/dist/matterbridgeEdge.js.map +1 -0
  23. package/dist/matterbridgeEndpoint.d.ts +5195 -0
  24. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  25. package/dist/matterbridgeEndpoint.js +2196 -0
  26. package/dist/matterbridgeEndpoint.js.map +1 -0
  27. package/dist/matterbridgeTypes.d.ts +6 -5
  28. package/dist/matterbridgeTypes.d.ts.map +1 -1
  29. package/dist/matterbridgeTypes.js +1 -1
  30. package/dist/matterbridgeTypes.js.map +1 -1
  31. package/dist/matterbridgeWebsocket.d.ts +49 -0
  32. package/dist/matterbridgeWebsocket.d.ts.map +1 -0
  33. package/dist/matterbridgeWebsocket.js +145 -0
  34. package/dist/matterbridgeWebsocket.js.map +1 -0
  35. package/dist/pluginManager.d.ts +134 -1
  36. package/dist/pluginManager.d.ts.map +1 -1
  37. package/dist/pluginManager.js +167 -14
  38. package/dist/pluginManager.js.map +1 -1
  39. package/frontend/build/asset-manifest.json +3 -3
  40. package/frontend/build/index.html +1 -1
  41. package/frontend/build/static/js/{main.96d6324b.js → main.045d08f7.js} +3 -3
  42. package/frontend/build/static/js/main.045d08f7.js.map +1 -0
  43. package/npm-shrinkwrap.json +838 -6222
  44. package/package.json +3 -78
  45. package/CODEOWNERS +0 -1
  46. package/dist/history/export.d.ts +0 -2
  47. package/dist/history/export.d.ts.map +0 -1
  48. package/dist/history/export.js +0 -2
  49. package/dist/history/export.js.map +0 -1
  50. package/frontend/build/static/js/main.96d6324b.js.map +0 -1
  51. /package/frontend/build/static/js/{main.96d6324b.js.LICENSE.txt → main.045d08f7.js.LICENSE.txt} +0 -0
@@ -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';
@@ -121,7 +122,6 @@ export class Matterbridge extends EventEmitter {
121
122
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
122
123
  plugins;
123
124
  devices;
124
- registeredDevices = [];
125
125
  nodeStorage;
126
126
  nodeContext;
127
127
  matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
@@ -131,8 +131,6 @@ export class Matterbridge extends EventEmitter {
131
131
  initialized = false;
132
132
  execRunningCount = 0;
133
133
  startMatterInterval;
134
- cleanupTimeout1;
135
- cleanupTimeout2;
136
134
  checkUpdateInterval;
137
135
  configureTimeout;
138
136
  reachabilityTimeout;
@@ -161,7 +159,10 @@ export class Matterbridge extends EventEmitter {
161
159
  // We load asyncronously so is private
162
160
  constructor() {
163
161
  super();
162
+ // Bind the handler to the instance
163
+ this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
164
164
  }
165
+ matterbridgeMessageHandler;
165
166
  /** ***********************************************************************************************************************************/
166
167
  /** loadInstance() and cleanup() methods */
167
168
  /** ***********************************************************************************************************************************/
@@ -225,6 +226,8 @@ export class Matterbridge extends EventEmitter {
225
226
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
226
227
  // this.log.debug('Creating node storage context for matterbridge');
227
228
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
229
+ // Check if the storage is corrupted and remove it
230
+ // TODO: Check if the storage is corrupted and remove it
228
231
  // Create matterbridge logger
229
232
  this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
230
233
  // Create the file logger for matterbridge (context: matterbridgeFileLog)
@@ -344,7 +347,7 @@ export class Matterbridge extends EventEmitter {
344
347
  // We don't do this when the add parameter is set because we shut down the process after adding the plugin
345
348
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
346
349
  try {
347
- await this.spawnCommand('npm', ['install', '-g', plugin.name]);
350
+ await this.spawnCommand('npm', ['install', '-g', '--omit=dev', plugin.name]);
348
351
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
349
352
  plugin.error = false;
350
353
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -473,28 +476,24 @@ export class Matterbridge extends EventEmitter {
473
476
  }
474
477
  if (getParameter('add')) {
475
478
  this.log.debug(`Adding plugin ${getParameter('add')}`);
476
- // await this.executeCommandLine(getParameter('add') as string, 'add');
477
479
  await this.plugins.add(getParameter('add'));
478
480
  this.emit('shutdown');
479
481
  return;
480
482
  }
481
483
  if (getParameter('remove')) {
482
484
  this.log.debug(`Removing plugin ${getParameter('remove')}`);
483
- // await this.executeCommandLine(getParameter('remove') as string, 'remove');
484
485
  await this.plugins.remove(getParameter('remove'));
485
486
  this.emit('shutdown');
486
487
  return;
487
488
  }
488
489
  if (getParameter('enable')) {
489
490
  this.log.debug(`Enabling plugin ${getParameter('enable')}`);
490
- // await this.executeCommandLine(getParameter('enable') as string, 'enable');
491
491
  await this.plugins.enable(getParameter('enable'));
492
492
  this.emit('shutdown');
493
493
  return;
494
494
  }
495
495
  if (getParameter('disable')) {
496
496
  this.log.debug(`Disabling plugin ${getParameter('disable')}`);
497
- // await this.executeCommandLine(getParameter('disable') as string, 'disable');
498
497
  await this.plugins.disable(getParameter('disable'));
499
498
  this.emit('shutdown');
500
499
  return;
@@ -518,14 +517,12 @@ export class Matterbridge extends EventEmitter {
518
517
  this.nodeContext = undefined;
519
518
  this.nodeStorage = undefined;
520
519
  this.plugins.clear();
521
- this.registeredDevices = [];
520
+ this.devices.clear();
522
521
  this.emit('shutdown');
523
522
  return;
524
523
  }
525
524
  // Start the matter storage and create the matterbridge context
526
525
  await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
527
- this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
528
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
529
526
  if (hasParameter('reset') && getParameter('reset') === undefined) {
530
527
  this.log.info('Resetting Matterbridge commissioning information...');
531
528
  await this.matterbridgeContext?.clearAll();
@@ -563,12 +560,13 @@ export class Matterbridge extends EventEmitter {
563
560
  this.getPluginLatestVersion(plugin);
564
561
  }
565
562
  }, 60 * 60 * 1000);
563
+ // Start the matterbridge in mode test
566
564
  if (hasParameter('test')) {
567
565
  this.bridgeMode = 'bridge';
568
566
  MatterbridgeDevice.bridgeMode = 'bridge';
569
- await this.startTest();
570
567
  return;
571
568
  }
569
+ // Start the matterbridge in mode controller
572
570
  if (hasParameter('controller')) {
573
571
  this.bridgeMode = 'controller';
574
572
  await this.startController();
@@ -579,91 +577,63 @@ export class Matterbridge extends EventEmitter {
579
577
  this.log.info('Setting default matterbridge start mode to bridge');
580
578
  await this.nodeContext?.set('bridgeMode', 'bridge');
581
579
  }
580
+ // Start matterbridge in bridge mode
582
581
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
583
582
  this.bridgeMode = 'bridge';
584
583
  MatterbridgeDevice.bridgeMode = 'bridge';
585
- if (!this.storageManager)
586
- throw new Error('No storage manager initialized');
587
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
588
- this.matterServer = this.createMatterServer(this.storageManager);
589
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
590
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
591
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
592
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
593
- this.log.debug('Adding matterbridge aggregator to commissioning server');
594
- this.commissioningServer.addDevice(this.matterAggregator);
595
- this.log.debug('Adding matterbridge commissioning server to matter server');
596
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
597
- for (const plugin of this.plugins) {
598
- plugin.configJson = await this.plugins.loadConfig(plugin);
599
- plugin.schemaJson = await this.plugins.loadSchema(plugin);
600
- // Check if the plugin is available
601
- if (!(await this.plugins.resolve(plugin.path))) {
602
- this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
603
- plugin.enabled = false;
604
- plugin.error = true;
605
- continue;
606
- }
607
- // Check if the plugin has a new version
608
- this.getPluginLatestVersion(plugin); // No await do it asyncronously
609
- if (!plugin.enabled) {
610
- this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
611
- continue;
612
- }
613
- plugin.error = false;
614
- plugin.locked = false;
615
- plugin.loaded = false;
616
- plugin.started = false;
617
- plugin.configured = false;
618
- plugin.connected = undefined;
619
- plugin.registeredDevices = undefined;
620
- plugin.addedDevices = undefined;
621
- plugin.qrPairingCode = undefined;
622
- plugin.manualPairingCode = undefined;
623
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
624
- }
584
+ this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
625
585
  await this.startBridge();
626
586
  return;
627
587
  }
588
+ // Start matterbridge in childbridge mode
628
589
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
629
590
  this.bridgeMode = 'childbridge';
630
591
  MatterbridgeDevice.bridgeMode = 'childbridge';
631
- if (!this.storageManager)
632
- throw new Error('No storage manager initialized');
633
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
634
- this.matterServer = this.createMatterServer(this.storageManager);
635
- for (const plugin of this.plugins) {
636
- plugin.configJson = await this.plugins.loadConfig(plugin);
637
- plugin.schemaJson = await this.plugins.loadSchema(plugin);
638
- // Check if the plugin is available
639
- if (!(await this.plugins.resolve(plugin.path))) {
640
- this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
641
- plugin.enabled = false;
642
- plugin.error = true;
643
- continue;
644
- }
645
- // Check if the plugin has a new version
646
- this.getPluginLatestVersion(plugin); // No await do it asyncronously
647
- if (!plugin.enabled) {
648
- this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
649
- continue;
650
- }
651
- plugin.error = false;
652
- plugin.locked = false;
653
- plugin.loaded = false;
654
- plugin.started = false;
655
- plugin.configured = false;
656
- plugin.connected = false;
657
- plugin.registeredDevices = undefined;
658
- plugin.addedDevices = undefined;
659
- plugin.qrPairingCode = undefined;
660
- plugin.manualPairingCode = undefined;
661
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
662
- }
592
+ this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
663
593
  await this.startChildbridge();
664
594
  return;
665
595
  }
666
596
  }
597
+ /**
598
+ * Asynchronously loads and starts the registered plugins.
599
+ *
600
+ * This method is responsible for initializing and staarting all enabled plugins.
601
+ * It ensures that each plugin is properly loaded and started before the ridge starts.
602
+ *
603
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
604
+ */
605
+ async startPlugins() {
606
+ // Check, load and start the plugins
607
+ for (const plugin of this.plugins) {
608
+ plugin.configJson = await this.plugins.loadConfig(plugin);
609
+ plugin.schemaJson = await this.plugins.loadSchema(plugin);
610
+ // Check if the plugin is available
611
+ if (!(await this.plugins.resolve(plugin.path))) {
612
+ this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
613
+ plugin.enabled = false;
614
+ plugin.error = true;
615
+ continue;
616
+ }
617
+ // Check if the plugin has a new version
618
+ this.getPluginLatestVersion(plugin); // No await do it asyncronously
619
+ if (!plugin.enabled) {
620
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
621
+ continue;
622
+ }
623
+ plugin.error = false;
624
+ plugin.locked = false;
625
+ plugin.loaded = false;
626
+ plugin.started = false;
627
+ plugin.configured = false;
628
+ plugin.connected = undefined;
629
+ plugin.registeredDevices = undefined;
630
+ plugin.addedDevices = undefined;
631
+ plugin.qrPairingCode = undefined;
632
+ plugin.manualPairingCode = undefined;
633
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
634
+ }
635
+ this.wssSendRefreshRequired();
636
+ }
667
637
  /**
668
638
  * Registers the signal handlers for SIGINT and SIGTERM.
669
639
  * When either of these signals are received, the cleanup method is called with an appropriate message.
@@ -1158,7 +1128,6 @@ export class Matterbridge extends EventEmitter {
1158
1128
  });
1159
1129
  this.webSocketServer = undefined;
1160
1130
  }
1161
- // this.cleanupTimeout1 = setTimeout(async () => {
1162
1131
  // Closing matter
1163
1132
  await this.stopMatterServer();
1164
1133
  // Closing matter storage
@@ -1175,8 +1144,8 @@ export class Matterbridge extends EventEmitter {
1175
1144
  if (this.nodeStorage && this.nodeContext) {
1176
1145
  this.log.info('Saving registered devices...');
1177
1146
  const serializedRegisteredDevices = [];
1178
- this.registeredDevices.forEach((registeredDevice) => {
1179
- const serializedMatterbridgeDevice = registeredDevice.device.serialize(registeredDevice.plugin);
1147
+ this.devices.forEach(async (device) => {
1148
+ const serializedMatterbridgeDevice = device.serialize();
1180
1149
  // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1181
1150
  if (serializedMatterbridgeDevice)
1182
1151
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
@@ -1203,9 +1172,7 @@ export class Matterbridge extends EventEmitter {
1203
1172
  this.log.error('Error saving registered devices: nodeContext not found!');
1204
1173
  }
1205
1174
  this.plugins.clear();
1206
- this.registeredDevices = [];
1207
- // this.log.info('Waiting for matter to deliver last messages...');
1208
- // this.cleanupTimeout2 = setTimeout(async () => {
1175
+ this.devices.clear();
1209
1176
  if (restart) {
1210
1177
  if (message === 'updating...') {
1211
1178
  this.log.info('Cleanup completed. Updating...');
@@ -1240,8 +1207,6 @@ export class Matterbridge extends EventEmitter {
1240
1207
  }
1241
1208
  this.hasCleanupStarted = false;
1242
1209
  this.initialized = false;
1243
- // }, 2 * 1000);
1244
- // }, 3 * 1000);
1245
1210
  }
1246
1211
  }
1247
1212
  /**
@@ -1251,17 +1216,17 @@ export class Matterbridge extends EventEmitter {
1251
1216
  * @returns {Promise<void>} - A promise that resolves when the device is added.
1252
1217
  */
1253
1218
  async addBridgedDevice(pluginName, device) {
1254
- this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1219
+ this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1255
1220
  // Check if the plugin is registered
1256
1221
  const plugin = this.plugins.get(pluginName);
1257
1222
  if (!plugin) {
1258
- this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1223
+ this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1259
1224
  return;
1260
1225
  }
1261
1226
  // Register and add the device to matterbridge aggregator in bridge mode
1262
1227
  if (this.bridgeMode === 'bridge') {
1263
1228
  if (!this.matterAggregator) {
1264
- this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1229
+ this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1265
1230
  return;
1266
1231
  }
1267
1232
  this.matterAggregator.addBridgedDevice(device);
@@ -1301,7 +1266,6 @@ export class Matterbridge extends EventEmitter {
1301
1266
  plugin.aggregator?.addBridgedDevice(device);
1302
1267
  }
1303
1268
  }
1304
- this.registeredDevices.push({ plugin: pluginName, device });
1305
1269
  if (plugin.registeredDevices !== undefined)
1306
1270
  plugin.registeredDevices++;
1307
1271
  if (plugin.addedDevices !== undefined)
@@ -1317,17 +1281,17 @@ export class Matterbridge extends EventEmitter {
1317
1281
  * @returns A Promise that resolves when the device is successfully removed.
1318
1282
  */
1319
1283
  async removeBridgedDevice(pluginName, device) {
1320
- this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1284
+ this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1321
1285
  // Check if the plugin is registered
1322
1286
  const plugin = this.plugins.get(pluginName);
1323
1287
  if (!plugin) {
1324
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1288
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1325
1289
  return;
1326
1290
  }
1327
1291
  // Remove the device from matterbridge aggregator in bridge mode
1328
1292
  if (this.bridgeMode === 'bridge') {
1329
1293
  if (!this.matterAggregator) {
1330
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1294
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1331
1295
  return;
1332
1296
  }
1333
1297
  if (device.number !== undefined) {
@@ -1337,13 +1301,7 @@ export class Matterbridge extends EventEmitter {
1337
1301
  // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1338
1302
  // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1339
1303
  this.matterAggregator?.removeBridgedDevice(device);
1340
- this.registeredDevices.forEach((registeredDevice, index) => {
1341
- if (registeredDevice.device === device) {
1342
- this.registeredDevices.splice(index, 1);
1343
- return;
1344
- }
1345
- });
1346
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1304
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1347
1305
  if (plugin.registeredDevices !== undefined)
1348
1306
  plugin.registeredDevices--;
1349
1307
  if (plugin.addedDevices !== undefined)
@@ -1353,34 +1311,22 @@ export class Matterbridge extends EventEmitter {
1353
1311
  if (this.bridgeMode === 'childbridge') {
1354
1312
  if (plugin.type === 'AccessoryPlatform') {
1355
1313
  if (!plugin.commissioningServer) {
1356
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1314
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1357
1315
  return;
1358
1316
  }
1359
- this.registeredDevices.forEach((registeredDevice, index) => {
1360
- if (registeredDevice.device === device) {
1361
- this.registeredDevices.splice(index, 1);
1362
- return;
1363
- }
1364
- });
1365
1317
  }
1366
1318
  else if (plugin.type === 'DynamicPlatform') {
1367
1319
  if (!plugin.aggregator) {
1368
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1320
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1369
1321
  return;
1370
1322
  }
1371
- this.registeredDevices.forEach((registeredDevice, index) => {
1372
- if (registeredDevice.device === device) {
1373
- this.registeredDevices.splice(index, 1);
1374
- return;
1375
- }
1376
- });
1377
1323
  if (device.number !== undefined) {
1378
1324
  device.setBridgedDeviceReachability(false);
1379
1325
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1380
1326
  }
1381
1327
  plugin.aggregator.removeBridgedDevice(device);
1382
1328
  }
1383
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1329
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1384
1330
  if (plugin.registeredDevices !== undefined)
1385
1331
  plugin.registeredDevices--;
1386
1332
  if (plugin.addedDevices !== undefined)
@@ -1403,18 +1349,11 @@ export class Matterbridge extends EventEmitter {
1403
1349
  */
1404
1350
  async removeAllBridgedDevices(pluginName) {
1405
1351
  this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1406
- const devicesToRemove = [];
1407
- for (const registeredDevice of this.registeredDevices) {
1408
- if (registeredDevice.plugin === pluginName) {
1409
- devicesToRemove.push(registeredDevice);
1352
+ this.devices.forEach(async (device) => {
1353
+ if (device.plugin === pluginName) {
1354
+ await this.removeBridgedDevice(pluginName, device);
1410
1355
  }
1411
- }
1412
- for (const registeredDevice of devicesToRemove) {
1413
- this.removeBridgedDevice(pluginName, registeredDevice.device);
1414
- }
1415
- }
1416
- async startTest() {
1417
- // Start the Matterbridge
1356
+ });
1418
1357
  }
1419
1358
  /**
1420
1359
  * Starts the Matterbridge in bridge mode.
@@ -1422,9 +1361,22 @@ export class Matterbridge extends EventEmitter {
1422
1361
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1423
1362
  */
1424
1363
  async startBridge() {
1425
- // Plugins are loaded and started by loadPlugin on startup and plugin.loaded and plugin.started are set to true
1426
1364
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1427
- this.log.debug('Starting startMatterInterval in bridge mode');
1365
+ if (!this.storageManager)
1366
+ throw new Error('No storage manager initialized');
1367
+ if (!this.matterbridgeContext)
1368
+ throw new Error('No storage context initialized');
1369
+ this.matterServer = this.createMatterServer(this.storageManager);
1370
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1371
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1372
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1373
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
1374
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
1375
+ this.commissioningServer.addDevice(this.matterAggregator);
1376
+ this.log.debug('Adding matterbridge commissioning server to matter server');
1377
+ await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1378
+ await this.startPlugins();
1379
+ this.log.debug('Starting start matter interval in bridge mode');
1428
1380
  let failCount = 0;
1429
1381
  this.startMatterInterval = setInterval(async () => {
1430
1382
  for (const plugin of this.plugins) {
@@ -1453,8 +1405,11 @@ export class Matterbridge extends EventEmitter {
1453
1405
  clearInterval(this.startMatterInterval);
1454
1406
  this.startMatterInterval = undefined;
1455
1407
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1408
+ // Start the Matter server
1456
1409
  await this.startMatterServer();
1457
1410
  this.log.notice('Matter server started');
1411
+ // Show the QR code for commissioning or log the already commissioned message
1412
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1458
1413
  // Configure the plugins
1459
1414
  this.configureTimeout = setTimeout(async () => {
1460
1415
  for (const plugin of this.plugins) {
@@ -1468,9 +1423,8 @@ export class Matterbridge extends EventEmitter {
1468
1423
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1469
1424
  }
1470
1425
  }
1426
+ this.wssSendRefreshRequired();
1471
1427
  }, 30 * 1000);
1472
- // Show the QR code for commissioning or log the already commissioned message
1473
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1474
1428
  // Setting reachability to true
1475
1429
  this.reachabilityTimeout = setTimeout(() => {
1476
1430
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
@@ -1487,9 +1441,12 @@ export class Matterbridge extends EventEmitter {
1487
1441
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1488
1442
  */
1489
1443
  async startChildbridge() {
1490
- // Plugins are loaded and started by loadPlugin on startup and plugin.loaded and plugin.started are set to true
1491
- // addDevice and addBridgedDeevice create the commissionig servers and add the devices to the the commissioning server or to the aggregator
1444
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1492
1445
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1446
+ if (!this.storageManager)
1447
+ throw new Error('No storage manager initialized');
1448
+ this.matterServer = this.createMatterServer(this.storageManager);
1449
+ await this.startPlugins();
1493
1450
  this.log.debug('Starting start matter interval in childbridge mode...');
1494
1451
  let failCount = 0;
1495
1452
  this.startMatterInterval = setInterval(async () => {
@@ -1501,7 +1458,7 @@ export class Matterbridge extends EventEmitter {
1501
1458
  if (plugin.error) {
1502
1459
  clearInterval(this.startMatterInterval);
1503
1460
  this.startMatterInterval = undefined;
1504
- this.log.debug('Cleared startMatterInterval interval for Matterbridge for plugin in error state');
1461
+ this.log.debug('Cleared startMatterInterval interval for a plugin in error state');
1505
1462
  this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
1506
1463
  this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
1507
1464
  this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
@@ -1523,6 +1480,7 @@ export class Matterbridge extends EventEmitter {
1523
1480
  clearInterval(this.startMatterInterval);
1524
1481
  this.startMatterInterval = undefined;
1525
1482
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1483
+ // Start the Matter server
1526
1484
  await this.startMatterServer();
1527
1485
  this.log.notice('Matter server started');
1528
1486
  // Configure the plugins
@@ -1538,6 +1496,7 @@ export class Matterbridge extends EventEmitter {
1538
1496
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1539
1497
  }
1540
1498
  }
1499
+ this.wssSendRefreshRequired();
1541
1500
  }, 30 * 1000);
1542
1501
  for (const plugin of this.plugins) {
1543
1502
  if (!plugin.enabled || plugin.error)
@@ -1778,7 +1737,7 @@ export class Matterbridge extends EventEmitter {
1778
1737
  await this.storageManager.initialize();
1779
1738
  this.log.debug('Storage initialized');
1780
1739
  if (storageType === 'json') {
1781
- await this.backupJsonMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1740
+ await this.backupMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1782
1741
  }
1783
1742
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1784
1743
  }
@@ -1787,6 +1746,8 @@ export class Matterbridge extends EventEmitter {
1787
1746
  this.log.error(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
1788
1747
  await this.cleanup('Storage initialize() error!');
1789
1748
  }
1749
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1750
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1790
1751
  }
1791
1752
  /**
1792
1753
  * Makes a backup copy of the specified matter JSON storage file.
@@ -1794,7 +1755,7 @@ export class Matterbridge extends EventEmitter {
1794
1755
  * @param storageName - The name of the JSON storage file to be backed up.
1795
1756
  * @param backupName - The name of the backup file to be created.
1796
1757
  */
1797
- async backupJsonMatterStorage(storageName, backupName) {
1758
+ async backupMatterStorage(storageName, backupName) {
1798
1759
  try {
1799
1760
  this.log.debug(`Making backup copy of ${storageName}`);
1800
1761
  await fs.copyFile(storageName, backupName);
@@ -2035,6 +1996,7 @@ export class Matterbridge extends EventEmitter {
2035
1996
  }
2036
1997
  }
2037
1998
  }
1999
+ this.wssSendRefreshRequired();
2038
2000
  },
2039
2001
  commissioningChangedCallback: async (fabricIndex) => {
2040
2002
  const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
@@ -2077,6 +2039,7 @@ export class Matterbridge extends EventEmitter {
2077
2039
  }
2078
2040
  }
2079
2041
  }
2042
+ this.wssSendRefreshRequired();
2080
2043
  },
2081
2044
  });
2082
2045
  if (this.passcode !== undefined)
@@ -2236,6 +2199,7 @@ export class Matterbridge extends EventEmitter {
2236
2199
  }
2237
2200
  }
2238
2201
  }
2202
+ this.wssSendRefreshRequired();
2239
2203
  }
2240
2204
  /**
2241
2205
  * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
@@ -2358,6 +2322,9 @@ export class Matterbridge extends EventEmitter {
2358
2322
  case 4701:
2359
2323
  vendorName = '(Tuya)';
2360
2324
  break;
2325
+ case 4718:
2326
+ vendorName = '(Xiaomi)';
2327
+ break;
2361
2328
  case 4742:
2362
2329
  vendorName = '(eWeLink)';
2363
2330
  break;
@@ -2452,7 +2419,7 @@ export class Matterbridge extends EventEmitter {
2452
2419
  this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
2453
2420
  if (code === 0) {
2454
2421
  if (cmdLine.startsWith('npm install -g'))
2455
- this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '')} installed correctly`);
2422
+ this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
2456
2423
  this.log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
2457
2424
  resolve();
2458
2425
  }
@@ -2533,7 +2500,33 @@ export class Matterbridge extends EventEmitter {
2533
2500
  // Send the message to all connected clients
2534
2501
  this.webSocketServer?.clients.forEach((client) => {
2535
2502
  if (client.readyState === WebSocket.OPEN) {
2536
- client.send(JSON.stringify({ level, time, name, message }));
2503
+ client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2504
+ }
2505
+ });
2506
+ }
2507
+ /**
2508
+ * Sends a need to refresh WebSocket message to all connected clients.
2509
+ *
2510
+ */
2511
+ wssSendRefreshRequired() {
2512
+ this.matterbridgeInformation.refreshRequired = true;
2513
+ // Send the message to all connected clients
2514
+ this.webSocketServer?.clients.forEach((client) => {
2515
+ if (client.readyState === WebSocket.OPEN) {
2516
+ client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
2517
+ }
2518
+ });
2519
+ }
2520
+ /**
2521
+ * Sends a need to restart WebSocket message to all connected clients.
2522
+ *
2523
+ */
2524
+ wssSendRestartRequired() {
2525
+ this.matterbridgeInformation.restartRequired = true;
2526
+ // Send the message to all connected clients
2527
+ this.webSocketServer?.clients.forEach((client) => {
2528
+ if (client.readyState === WebSocket.OPEN) {
2529
+ client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
2537
2530
  }
2538
2531
  });
2539
2532
  }
@@ -2652,7 +2645,7 @@ export class Matterbridge extends EventEmitter {
2652
2645
  }
2653
2646
  if (initializeError)
2654
2647
  return;
2655
- // Createe a WebSocket server and attach it to the http server
2648
+ // Createe a WebSocket server and attach it to the http or https server
2656
2649
  const wssPort = port;
2657
2650
  const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
2658
2651
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
@@ -2662,6 +2655,14 @@ export class Matterbridge extends EventEmitter {
2662
2655
  this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2663
2656
  ws.on('message', (message) => {
2664
2657
  this.log.debug(`WebSocket client message: ${message}`);
2658
+ this.matterbridgeMessageHandler(ws, message);
2659
+ });
2660
+ ws.on('ping', () => {
2661
+ this.log.debug('WebSocket client ping');
2662
+ ws.pong();
2663
+ });
2664
+ ws.on('pong', () => {
2665
+ this.log.debug('WebSocket client pong');
2665
2666
  });
2666
2667
  ws.on('close', () => {
2667
2668
  this.log.info('WebSocket client disconnected');
@@ -2724,12 +2725,8 @@ export class Matterbridge extends EventEmitter {
2724
2725
  this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
2725
2726
  this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
2726
2727
  this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
2727
- // this.matterbridgeInformation.matterbridgeSessionInformations = this.matterbridgeSessionInformations;
2728
2728
  this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
2729
- // console.log('this.matterbridgeSessionInformations:', this.matterbridgeSessionInformations);
2730
- if (this.profile)
2731
- this.matterbridgeInformation.profile = this.profile;
2732
- // const response = { wssHost, ssl: hasParameter('ssl'), qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2729
+ this.matterbridgeInformation.profile = this.profile;
2733
2730
  const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2734
2731
  // this.log.debug('Response:', debugStringify(response));
2735
2732
  res.json(response);
@@ -2745,21 +2742,21 @@ export class Matterbridge extends EventEmitter {
2745
2742
  this.expressApp.get('/api/devices', (req, res) => {
2746
2743
  this.log.debug('The frontend sent /api/devices');
2747
2744
  const data = [];
2748
- this.registeredDevices.forEach((registeredDevice) => {
2749
- let name = registeredDevice.device.getClusterServer(BasicInformationCluster)?.attributes.nodeLabel?.getLocal();
2745
+ this.devices.forEach(async (device) => {
2746
+ let name = device.getClusterServer(BasicInformationCluster)?.attributes.nodeLabel?.getLocal();
2750
2747
  if (!name)
2751
- name = registeredDevice.device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.nodeLabel?.getLocal() ?? 'Unknown';
2752
- let serial = registeredDevice.device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
2748
+ name = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.nodeLabel?.getLocal() ?? 'Unknown';
2749
+ let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
2753
2750
  if (!serial)
2754
- serial = registeredDevice.device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
2755
- let uniqueId = registeredDevice.device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
2751
+ serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
2752
+ let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
2756
2753
  if (!uniqueId)
2757
- uniqueId = registeredDevice.device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
2758
- const cluster = this.getClusterTextFromDevice(registeredDevice.device);
2754
+ uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
2755
+ const cluster = this.getClusterTextFromDevice(device);
2759
2756
  data.push({
2760
- pluginName: registeredDevice.plugin,
2761
- type: registeredDevice.device.name + ' (0x' + registeredDevice.device.deviceType.toString(16).padStart(4, '0') + ')',
2762
- endpoint: registeredDevice.device.number,
2757
+ pluginName: device.plugin ?? 'Unknown',
2758
+ type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
2759
+ endpoint: device.number,
2763
2760
  name,
2764
2761
  serial,
2765
2762
  uniqueId,
@@ -2779,9 +2776,9 @@ export class Matterbridge extends EventEmitter {
2779
2776
  return;
2780
2777
  }
2781
2778
  const data = [];
2782
- this.registeredDevices.forEach((registeredDevice) => {
2783
- if (registeredDevice.plugin === selectedPluginName && registeredDevice.device.number === selectedDeviceEndpoint) {
2784
- const clusterServers = registeredDevice.device.getAllClusterServers();
2779
+ this.devices.forEach(async (device) => {
2780
+ if (device.plugin === selectedPluginName && device.number === selectedDeviceEndpoint) {
2781
+ const clusterServers = device.getAllClusterServers();
2785
2782
  clusterServers.forEach((clusterServer) => {
2786
2783
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
2787
2784
  if (clusterServer.name === 'EveHistory')
@@ -2800,7 +2797,7 @@ export class Matterbridge extends EventEmitter {
2800
2797
  // console.log(error);
2801
2798
  }
2802
2799
  data.push({
2803
- endpoint: registeredDevice.device.number ? registeredDevice.device.number.toString() : '...',
2800
+ endpoint: device.number ? device.number.toString() : '...',
2804
2801
  clusterName: clusterServer.name,
2805
2802
  clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
2806
2803
  attributeName: key,
@@ -2809,8 +2806,8 @@ export class Matterbridge extends EventEmitter {
2809
2806
  });
2810
2807
  });
2811
2808
  });
2812
- registeredDevice.device.getChildEndpoints().forEach((childEndpoint) => {
2813
- const name = registeredDevice.device.getChildEndpointName(childEndpoint);
2809
+ device.getChildEndpoints().forEach((childEndpoint) => {
2810
+ const name = childEndpoint.uniqueStorageKey;
2814
2811
  const clusterServers = childEndpoint.getAllClusterServers();
2815
2812
  clusterServers.forEach((clusterServer) => {
2816
2813
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
@@ -2844,7 +2841,7 @@ export class Matterbridge extends EventEmitter {
2844
2841
  });
2845
2842
  res.json(data);
2846
2843
  });
2847
- // Endpoint to send the log
2844
+ // Endpoint to view the log
2848
2845
  this.expressApp.get('/api/view-log', async (req, res) => {
2849
2846
  this.log.debug('The frontend sent /api/log');
2850
2847
  try {
@@ -2966,7 +2963,7 @@ export class Matterbridge extends EventEmitter {
2966
2963
  // Handle the command setbridgemode from Settings
2967
2964
  if (command === 'setbridgemode') {
2968
2965
  this.log.debug(`setbridgemode: ${param}`);
2969
- this.matterbridgeInformation.restartRequired = true;
2966
+ this.wssSendRestartRequired();
2970
2967
  await this.nodeContext?.set('bridgeMode', param);
2971
2968
  res.json({ message: 'Command received' });
2972
2969
  return;
@@ -3138,7 +3135,7 @@ export class Matterbridge extends EventEmitter {
3138
3135
  if (command === 'update') {
3139
3136
  this.log.info('Updating matterbridge...');
3140
3137
  try {
3141
- await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--verbose']);
3138
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
3142
3139
  this.log.info('Matterbridge has been updated. Full restart required.');
3143
3140
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3144
3141
  }
@@ -3146,7 +3143,7 @@ export class Matterbridge extends EventEmitter {
3146
3143
  this.log.error('Error updating matterbridge');
3147
3144
  }
3148
3145
  await this.updateProcess();
3149
- this.matterbridgeInformation.restartRequired = true;
3146
+ this.wssSendRestartRequired();
3150
3147
  res.json({ message: 'Command received' });
3151
3148
  return;
3152
3149
  }
@@ -3164,7 +3161,7 @@ export class Matterbridge extends EventEmitter {
3164
3161
  return;
3165
3162
  this.plugins.saveConfigFromJson(plugin, req.body);
3166
3163
  }
3167
- this.matterbridgeInformation.restartRequired = true;
3164
+ this.wssSendRestartRequired();
3168
3165
  res.json({ message: 'Command received' });
3169
3166
  return;
3170
3167
  }
@@ -3173,14 +3170,14 @@ export class Matterbridge extends EventEmitter {
3173
3170
  param = param.replace(/\*/g, '\\');
3174
3171
  this.log.info(`Installing plugin ${plg}${param}${nf}...`);
3175
3172
  try {
3176
- await this.spawnCommand('npm', ['install', '-g', param, '--verbose']);
3173
+ await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
3177
3174
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3178
3175
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3179
3176
  }
3180
3177
  catch (error) {
3181
3178
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
3182
3179
  }
3183
- this.matterbridgeInformation.restartRequired = true;
3180
+ this.wssSendRestartRequired();
3184
3181
  // Also add the plugin to matterbridge so no return!
3185
3182
  // res.json({ message: 'Command received' });
3186
3183
  // return;
@@ -3193,6 +3190,7 @@ export class Matterbridge extends EventEmitter {
3193
3190
  this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
3194
3191
  }
3195
3192
  res.json({ message: 'Command received' });
3193
+ this.wssSendRefreshRequired();
3196
3194
  return;
3197
3195
  }
3198
3196
  // Handle the command removeplugin from Home
@@ -3206,6 +3204,7 @@ export class Matterbridge extends EventEmitter {
3206
3204
  await this.plugins.remove(param);
3207
3205
  }
3208
3206
  res.json({ message: 'Command received' });
3207
+ this.wssSendRefreshRequired();
3209
3208
  return;
3210
3209
  }
3211
3210
  // Handle the command enableplugin from Home
@@ -3230,6 +3229,7 @@ export class Matterbridge extends EventEmitter {
3230
3229
  }
3231
3230
  }
3232
3231
  res.json({ message: 'Command received' });
3232
+ this.wssSendRefreshRequired();
3233
3233
  return;
3234
3234
  }
3235
3235
  // Handle the command disableplugin from Home
@@ -3245,6 +3245,7 @@ export class Matterbridge extends EventEmitter {
3245
3245
  }
3246
3246
  }
3247
3247
  res.json({ message: 'Command received' });
3248
+ this.wssSendRefreshRequired();
3248
3249
  return;
3249
3250
  }
3250
3251
  });