@matterbridge/core 3.6.2-dev-20260314-f95be8a → 3.6.2-dev-20260316-0b03ae0

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.
@@ -22,6 +22,7 @@ export declare class Frontend extends EventEmitter<FrontendEvents> {
22
22
  private httpsServer;
23
23
  private webSocketServer;
24
24
  private readonly server;
25
+ private serverFetchTimeout;
25
26
  private readonly debug;
26
27
  private readonly verbose;
27
28
  constructor(matterbridge: Matterbridge);
@@ -56,5 +57,7 @@ export declare class Frontend extends EventEmitter<FrontendEvents> {
56
57
  wssSendCloseSnackbarMessage(message: string): void;
57
58
  wssSendAttributeChangedMessage(plugin: string, serialNumber: string, uniqueId: string, number: EndpointNumber, id: string, cluster: string, attribute: string, value: number | string | boolean | null): void;
58
59
  wssBroadcastMessage(msg: WsMessageBroadcast): void;
60
+ spawn(command: 'install' | 'uninstall', packageName: string): void;
61
+ zip(command: 'zip' | 'verify' | 'unzip', archivePath: string, sourcePaths: string[], destinationPath: string): void;
59
62
  }
60
63
  export {};
package/dist/frontend.js CHANGED
@@ -16,7 +16,6 @@ import { inspectError } from '@matterbridge/utils/error';
16
16
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
17
17
  import { isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString } from '@matterbridge/utils/validate';
18
18
  import { wait, withTimeout } from '@matterbridge/utils/wait';
19
- import { createZip } from '@matterbridge/utils/zip';
20
19
  import { AnsiLogger, CYAN, db, debugStringify, er, nf, nt, rs, stringify, UNDERLINE, UNDERLINEOFF, YELLOW } from 'node-ansi-logger';
21
20
  import { cliEmitter, lastOsCpuUsage, lastProcessCpuUsage } from './cliEmitter.js';
22
21
  import { generateHistoryPage } from './cliHistory.js';
@@ -33,6 +32,7 @@ export class Frontend extends EventEmitter {
33
32
  httpsServer;
34
33
  webSocketServer;
35
34
  server;
35
+ serverFetchTimeout = 2000;
36
36
  debug = hasParameter('debug') || hasParameter('verbose');
37
37
  verbose = hasParameter('verbose');
38
38
  constructor(matterbridge) {
@@ -134,6 +134,29 @@ export class Frontend extends EventEmitter {
134
134
  }
135
135
  }
136
136
  break;
137
+ case 'manager_archive_response':
138
+ if (msg.result &&
139
+ msg.result.success &&
140
+ isValidString(msg.result.command) &&
141
+ isValidString(msg.result.archivePath) &&
142
+ isValidArray(msg.result.sourcePaths) &&
143
+ isValidString(msg.result.destinationPath)) {
144
+ this.log.debug(`***Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
145
+ this.wssBroadcastMessage({
146
+ id: 0,
147
+ src: 'Matterbridge',
148
+ dst: 'Frontend',
149
+ method: 'archive',
150
+ success: true,
151
+ response: {
152
+ command: msg.result.command,
153
+ archivePath: msg.result.archivePath,
154
+ sourcePaths: msg.result.sourcePaths,
155
+ destinationPath: msg.result.destinationPath,
156
+ },
157
+ });
158
+ }
159
+ break;
137
160
  }
138
161
  }
139
162
  }
@@ -496,6 +519,31 @@ export class Frontend extends EventEmitter {
496
519
  res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
497
520
  }
498
521
  });
522
+ this.expressApp.get('/api/download-mblog', async (req, res) => {
523
+ this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
524
+ if (!this.validateReq(req, res))
525
+ return;
526
+ const fs = await import('node:fs');
527
+ try {
528
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
529
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
530
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
531
+ }
532
+ catch (error) {
533
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
534
+ this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
535
+ }
536
+ res.type('text/plain; charset=utf-8');
537
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
538
+ if (error) {
539
+ this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
540
+ res.status(500).send('Error downloading the matterbridge log file');
541
+ }
542
+ else {
543
+ this.log.debug(`Matterbridge log file ${MATTERBRIDGE_LOGGER_FILE} downloaded successfully`);
544
+ }
545
+ });
546
+ });
499
547
  this.expressApp.get('/api/view-mjlog', async (req, res) => {
500
548
  this.log.debug('The frontend sent /api/view-mjlog');
501
549
  if (!this.validateReq(req, res))
@@ -511,6 +559,31 @@ export class Frontend extends EventEmitter {
511
559
  res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
512
560
  }
513
561
  });
562
+ this.expressApp.get('/api/download-mjlog', async (req, res) => {
563
+ this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE)}`);
564
+ if (!this.validateReq(req, res))
565
+ return;
566
+ const fs = await import('node:fs');
567
+ try {
568
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
569
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
570
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
571
+ }
572
+ catch (error) {
573
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
574
+ this.log.debug(`Error in /api/download-mjlog: ${error instanceof Error ? error.message : error}`);
575
+ }
576
+ res.type('text/plain; charset=utf-8');
577
+ res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
578
+ if (error) {
579
+ this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
580
+ res.status(500).send('Error downloading the matter log file');
581
+ }
582
+ else {
583
+ this.log.debug(`Matter log file ${MATTER_LOGGER_FILE} downloaded successfully`);
584
+ }
585
+ });
586
+ });
514
587
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
515
588
  this.log.debug('The frontend sent /api/view-diagnostic');
516
589
  if (!this.validateReq(req, res))
@@ -547,6 +620,9 @@ export class Frontend extends EventEmitter {
547
620
  this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
548
621
  res.status(500).send('Error downloading the diagnostic log file');
549
622
  }
623
+ else {
624
+ this.log.debug(`Diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE} downloaded successfully`);
625
+ }
550
626
  });
551
627
  });
552
628
  this.expressApp.get('/api/viewhistory', async (req, res) => {
@@ -579,6 +655,9 @@ export class Frontend extends EventEmitter {
579
655
  this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
580
656
  res.status(500).send('Error downloading history file');
581
657
  }
658
+ else {
659
+ this.log.debug(`History file ${MATTERBRIDGE_HISTORY_FILE} downloaded successfully`);
660
+ }
582
661
  });
583
662
  }
584
663
  catch (error) {
@@ -586,84 +665,18 @@ export class Frontend extends EventEmitter {
586
665
  res.status(500).send('Error reading history file.');
587
666
  }
588
667
  });
589
- this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
590
- this.log.debug('The frontend sent /api/shellyviewsystemlog');
591
- if (!this.validateReq(req, res))
592
- return;
593
- try {
594
- const fs = await import('node:fs');
595
- const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
596
- res.type('text/plain; charset=utf-8');
597
- res.send(data);
598
- }
599
- catch (error) {
600
- this.log.error(`Error reading shelly log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
601
- res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
602
- }
603
- });
604
- this.expressApp.get('/api/download-mblog', async (req, res) => {
605
- this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
606
- if (!this.validateReq(req, res))
607
- return;
608
- const fs = await import('node:fs');
609
- try {
610
- await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
611
- const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
612
- await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
613
- }
614
- catch (error) {
615
- await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
616
- this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
617
- }
618
- res.type('text/plain; charset=utf-8');
619
- res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
620
- if (error) {
621
- this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
622
- res.status(500).send('Error downloading the matterbridge log file');
623
- }
624
- });
625
- });
626
- this.expressApp.get('/api/download-mjlog', async (req, res) => {
627
- this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
668
+ this.expressApp.get('/api/download-backup', async (req, res) => {
669
+ this.log.debug('The frontend sent /api/download-backup');
628
670
  if (!this.validateReq(req, res))
629
671
  return;
630
- const fs = await import('node:fs');
631
- try {
632
- await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
633
- const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
634
- await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
635
- }
636
- catch (error) {
637
- await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
638
- this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
639
- }
640
- res.type('text/plain; charset=utf-8');
641
- res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
672
+ res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
673
+ this.wssSendCloseSnackbarMessage('Creating matterbridge backup...');
642
674
  if (error) {
643
- this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
644
- res.status(500).send('Error downloading the matter log file');
675
+ this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
676
+ res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
645
677
  }
646
- });
647
- });
648
- this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
649
- this.log.debug('The frontend sent /api/shellydownloadsystemlog');
650
- if (!this.validateReq(req, res))
651
- return;
652
- const fs = await import('node:fs');
653
- try {
654
- await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
655
- const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
656
- await fs.promises.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
657
- }
658
- catch (error) {
659
- await fs.promises.writeFile(path.join(os.tmpdir(), 'shelly.log'), 'Create the Shelly system log before downloading it.', 'utf-8');
660
- this.log.debug(`Error in /api/shellydownloadsystemlog: ${error instanceof Error ? error.message : error}`);
661
- }
662
- res.type('text/plain; charset=utf-8');
663
- res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
664
- if (error) {
665
- this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
666
- res.status(500).send('Error downloading Shelly system log file');
678
+ else {
679
+ this.log.debug('Backup matterbridge.backup.zip downloaded successfully');
667
680
  }
668
681
  });
669
682
  });
@@ -671,58 +684,59 @@ export class Frontend extends EventEmitter {
671
684
  this.log.debug('The frontend sent /api/download-mbstorage');
672
685
  if (!this.validateReq(req, res))
673
686
  return;
674
- await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
675
687
  res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
688
+ this.wssSendCloseSnackbarMessage('Creating matterbridge storage backup...');
676
689
  if (error) {
677
690
  this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
678
691
  res.status(500).send('Error downloading the matterbridge storage file');
679
692
  }
693
+ else {
694
+ this.log.debug(`Matterbridge storage matterbridge.${NODE_STORAGE_DIR}.zip downloaded successfully`);
695
+ }
680
696
  });
681
697
  });
682
698
  this.expressApp.get('/api/download-mjstorage', async (req, res) => {
683
699
  this.log.debug('The frontend sent /api/download-mjstorage');
684
700
  if (!this.validateReq(req, res))
685
701
  return;
686
- await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
687
702
  res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
703
+ this.wssSendCloseSnackbarMessage('Creating matter storage backup...');
688
704
  if (error) {
689
705
  this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
690
706
  res.status(500).send('Error downloading the matter storage zip file');
691
707
  }
708
+ else {
709
+ this.log.debug(`Matter storage matterbridge.${MATTER_STORAGE_NAME}.zip downloaded successfully`);
710
+ }
692
711
  });
693
712
  });
694
713
  this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
695
714
  this.log.debug('The frontend sent /api/download-pluginstorage');
696
715
  if (!this.validateReq(req, res))
697
716
  return;
698
- await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
699
717
  res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
718
+ this.wssSendCloseSnackbarMessage('Creating plugin backup...');
700
719
  if (error) {
701
720
  this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
702
721
  res.status(500).send('Error downloading the matterbridge plugin storage file');
703
722
  }
723
+ else {
724
+ this.log.debug('Plugin storage matterbridge.pluginstorage.zip downloaded successfully');
725
+ }
704
726
  });
705
727
  });
706
728
  this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
707
729
  this.log.debug('The frontend sent /api/download-pluginconfig');
708
730
  if (!this.validateReq(req, res))
709
731
  return;
710
- await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
711
732
  res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
733
+ this.wssSendCloseSnackbarMessage('Creating config backup...');
712
734
  if (error) {
713
735
  this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
714
736
  res.status(500).send('Error downloading the matterbridge plugin config file');
715
737
  }
716
- });
717
- });
718
- this.expressApp.get('/api/download-backup', async (req, res) => {
719
- this.log.debug('The frontend sent /api/download-backup');
720
- if (!this.validateReq(req, res))
721
- return;
722
- res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
723
- if (error) {
724
- this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
725
- res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
738
+ else {
739
+ this.log.debug('Plugin config matterbridge.pluginconfig.zip downloaded successfully');
726
740
  }
727
741
  });
728
742
  });
@@ -1231,11 +1245,7 @@ export class Frontend extends EventEmitter {
1231
1245
  };
1232
1246
  try {
1233
1247
  data = JSON.parse(message.toString());
1234
- if (!isValidNumber(data.id) ||
1235
- !isValidString(data.dst) ||
1236
- !isValidString(data.src) ||
1237
- !isValidString(data.method) ||
1238
- data.dst !== 'Matterbridge') {
1248
+ if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
1239
1249
  this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
1240
1250
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
1241
1251
  return;
@@ -1266,21 +1276,7 @@ export class Frontend extends EventEmitter {
1266
1276
  else if (data.method === '/api/install') {
1267
1277
  if (isValidString(data.params.packageName, 12) && isValidBoolean(data.params.restart)) {
1268
1278
  this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
1269
- this.server.request({
1270
- type: 'manager_run',
1271
- src: 'frontend',
1272
- dst: 'manager',
1273
- params: {
1274
- name: 'SpawnCommand',
1275
- workerData: {
1276
- threadName: 'SpawnCommand',
1277
- command: 'npm',
1278
- args: ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'],
1279
- packageCommand: 'install',
1280
- packageName: data.params.packageName,
1281
- },
1282
- },
1283
- });
1279
+ this.spawn('install', data.params.packageName);
1284
1280
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1285
1281
  }
1286
1282
  else {
@@ -1290,21 +1286,7 @@ export class Frontend extends EventEmitter {
1290
1286
  else if (data.method === '/api/uninstall') {
1291
1287
  if (isValidString(data.params.packageName, 12)) {
1292
1288
  this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1293
- this.server.request({
1294
- type: 'manager_run',
1295
- src: 'frontend',
1296
- dst: 'manager',
1297
- params: {
1298
- name: 'SpawnCommand',
1299
- workerData: {
1300
- threadName: 'SpawnCommand',
1301
- command: 'npm',
1302
- args: ['uninstall', '-g', data.params.packageName, '--omit=dev', '--verbose'],
1303
- packageCommand: 'uninstall',
1304
- packageName: data.params.packageName,
1305
- },
1306
- },
1307
- });
1289
+ this.spawn('uninstall', data.params.packageName);
1308
1290
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1309
1291
  }
1310
1292
  else {
@@ -1318,26 +1300,24 @@ export class Frontend extends EventEmitter {
1318
1300
  this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1319
1301
  return;
1320
1302
  }
1321
- this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
1303
+ this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 0);
1322
1304
  this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
1323
1305
  data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
1324
- const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
1306
+ const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, this.serverFetchTimeout)).result.plugin;
1325
1307
  if (plugin) {
1326
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1308
+ this.wssSendCloseSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`);
1327
1309
  this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
1328
- this.matterbridge.plugins
1329
- .load(plugin)
1330
- .then(() => {
1331
- this.wssSendRefreshRequired('plugins');
1332
- this.wssSendRefreshRequired('devices');
1333
- this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1334
- return;
1335
- })
1336
- .catch((_error) => { });
1310
+ await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, this.serverFetchTimeout);
1311
+ this.wssSendRestartRequired();
1312
+ this.wssSendRefreshRequired('plugins');
1313
+ this.wssSendRefreshRequired('devices');
1314
+ this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
1315
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1337
1316
  }
1338
1317
  else {
1339
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1318
+ this.wssSendCloseSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`);
1340
1319
  this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
1320
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
1341
1321
  }
1342
1322
  }
1343
1323
  else if (data.method === '/api/removeplugin') {
@@ -1345,11 +1325,17 @@ export class Frontend extends EventEmitter {
1345
1325
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/removeplugin' });
1346
1326
  return;
1347
1327
  }
1348
- this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
1328
+ this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 0);
1349
1329
  this.log.debug(`Removing plugin ${data.params.pluginName}...`);
1350
- const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1351
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
1352
- await this.matterbridge.plugins.remove(data.params.pluginName);
1330
+ const devices = (await this.server.fetch({ type: 'devices_basearray', src: this.server.name, dst: 'devices', params: { pluginName: data.params.pluginName } }, this.serverFetchTimeout)).result.devices;
1331
+ for (const device of devices.filter((d) => d.mode === 'server')) {
1332
+ if (device.uniqueId)
1333
+ await this.server.fetch({ type: 'matterbridge_stop_device_server', src: this.server.name, dst: 'matterbridge', params: { deviceUniqueId: device.uniqueId } }, this.serverFetchTimeout);
1334
+ }
1335
+ await this.server.fetch({ type: 'plugins_shutdown', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName, reason: 'The plugin has been removed.', removeAllDevices: true, force: true } }, this.serverFetchTimeout);
1336
+ await this.server.fetch({ type: 'matterbridge_stop_plugin_server', src: this.server.name, dst: 'matterbridge', params: { pluginName: data.params.pluginName } }, this.serverFetchTimeout);
1337
+ await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, this.serverFetchTimeout);
1338
+ this.wssSendCloseSnackbarMessage(`Removing plugin ${data.params.pluginName}...`);
1353
1339
  this.wssSendSnackbarMessage(`Removed plugin ${data.params.pluginName}`, 5, 'success');
1354
1340
  this.wssSendRefreshRequired('plugins');
1355
1341
  this.wssSendRefreshRequired('devices');
@@ -1361,44 +1347,35 @@ export class Frontend extends EventEmitter {
1361
1347
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/enableplugin' });
1362
1348
  return;
1363
1349
  }
1364
- const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1365
- plugin.locked = undefined;
1366
- plugin.error = undefined;
1367
- plugin.loaded = undefined;
1368
- plugin.started = undefined;
1369
- plugin.configured = undefined;
1370
- plugin.platform = undefined;
1371
- plugin.registeredDevices = undefined;
1372
- plugin.matter = undefined;
1373
- await this.matterbridge.plugins.enable(data.params.pluginName);
1350
+ await this.server.fetch({ type: 'plugins_enable', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, this.serverFetchTimeout);
1374
1351
  this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
1375
- setImmediate(async () => {
1376
- await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
1377
- if (plugin.serverNode)
1378
- await this.matterbridge.startServerNode(plugin.serverNode);
1379
- for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
1380
- await this.matterbridge.startServerNode(device.serverNode);
1381
- this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
1382
- this.wssSendRefreshRequired('plugins');
1383
- this.wssSendRefreshRequired('devices');
1384
- sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, success: true });
1385
- });
1352
+ await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName } }, this.serverFetchTimeout * 10);
1353
+ await this.server.fetch({ type: 'plugins_start', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName } }, this.serverFetchTimeout * 10);
1354
+ await this.server.fetch({ type: 'plugins_configure', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName } }, this.serverFetchTimeout * 10);
1355
+ await this.server.fetch({ type: 'matterbridge_start_plugin_server', src: this.server.name, dst: 'matterbridge', params: { pluginName: data.params.pluginName } }, this.serverFetchTimeout);
1356
+ const devices = (await this.server.fetch({ type: 'devices_basearray', src: this.server.name, dst: 'devices', params: { pluginName: data.params.pluginName } }, this.serverFetchTimeout)).result.devices;
1357
+ for (const device of devices.filter((d) => d.mode === 'server')) {
1358
+ if (device.uniqueId)
1359
+ await this.server.fetch({ type: 'matterbridge_start_device_server', src: this.server.name, dst: 'matterbridge', params: { deviceUniqueId: device.uniqueId } }, this.serverFetchTimeout);
1360
+ }
1361
+ this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
1362
+ this.wssSendRefreshRequired('plugins');
1363
+ this.wssSendRefreshRequired('devices');
1364
+ sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, success: true });
1386
1365
  }
1387
1366
  else if (data.method === '/api/disableplugin') {
1388
1367
  if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
1389
1368
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/disableplugin' });
1390
1369
  return;
1391
1370
  }
1392
- const plugin = this.matterbridge.plugins.get(data.params.pluginName);
1393
- for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
1394
- await this.matterbridge.stopServerNode(device.serverNode);
1395
- device.serverNode = undefined;
1371
+ const devices = (await this.server.fetch({ type: 'devices_basearray', src: this.server.name, dst: 'devices', params: { pluginName: data.params.pluginName } }, this.serverFetchTimeout)).result.devices;
1372
+ for (const device of devices.filter((d) => d.mode === 'server')) {
1373
+ if (device.uniqueId)
1374
+ await this.server.fetch({ type: 'matterbridge_stop_device_server', src: this.server.name, dst: 'matterbridge', params: { deviceUniqueId: device.uniqueId } }, this.serverFetchTimeout);
1396
1375
  }
1397
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
1398
- await this.matterbridge.plugins.disable(data.params.pluginName);
1399
- if (plugin.serverNode)
1400
- await this.matterbridge.stopServerNode(plugin.serverNode);
1401
- plugin.serverNode = undefined;
1376
+ await this.server.fetch({ type: 'plugins_shutdown', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName, reason: 'The plugin has been disabled.', removeAllDevices: true, force: true } }, this.serverFetchTimeout * 10);
1377
+ await this.server.fetch({ type: 'matterbridge_stop_plugin_server', src: this.server.name, dst: 'matterbridge', params: { pluginName: data.params.pluginName } }, this.serverFetchTimeout);
1378
+ await this.server.fetch({ type: 'plugins_disable', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, this.serverFetchTimeout);
1402
1379
  this.wssSendSnackbarMessage(`Disabled plugin ${data.params.pluginName}`, 5, 'success');
1403
1380
  this.wssSendRefreshRequired('plugins');
1404
1381
  this.wssSendRefreshRequired('devices');
@@ -1490,12 +1467,35 @@ export class Frontend extends EventEmitter {
1490
1467
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1491
1468
  }
1492
1469
  else if (data.method === '/api/create-backup') {
1493
- this.wssSendSnackbarMessage('Creating backup...', 0);
1494
- this.log.notice(`Creating the backup...`);
1495
- await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory), path.join(this.matterbridge.matterbridgeCertDirectory));
1496
- this.log.notice(`Backup ready to be downloaded.`);
1497
- this.wssSendCloseSnackbarMessage('Creating backup...');
1498
- this.wssSendSnackbarMessage('Backup ready to be downloaded', 10);
1470
+ this.wssSendSnackbarMessage('Creating matterbridge backup...', 0);
1471
+ this.log.notice(`Creating matterbridge backup...`);
1472
+ this.zip('zip', path.join(os.tmpdir(), `matterbridge.backup.zip`), [this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgePluginDirectory, this.matterbridge.matterbridgeCertDirectory], '');
1473
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1474
+ }
1475
+ else if (data.method === '/api/create-matterbridge-storage-backup') {
1476
+ this.wssSendSnackbarMessage('Creating matterbridge storage backup...', 0);
1477
+ this.log.notice(`Creating matterbridge storage backup...`);
1478
+ this.zip('zip', path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), [path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR)], '');
1479
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1480
+ }
1481
+ else if (data.method === '/api/create-matter-storage-backup') {
1482
+ this.wssSendSnackbarMessage('Creating matter storage backup...', 0);
1483
+ this.log.notice(`Creating matter storage backup...`);
1484
+ this.zip('zip', path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), [path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME)], '');
1485
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1486
+ }
1487
+ else if (data.method === '/api/create-plugin-backup') {
1488
+ this.wssSendSnackbarMessage('Creating plugin backup...', 0);
1489
+ this.log.notice(`Creating plugin backup...`);
1490
+ this.zip('zip', path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), [this.matterbridge.matterbridgePluginDirectory], '');
1491
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1492
+ }
1493
+ else if (data.method === '/api/create-config-backup') {
1494
+ this.wssSendSnackbarMessage('Creating config backup...', 0);
1495
+ this.log.notice(`Creating config backup...`);
1496
+ const plugins = (await this.server.fetch({ type: 'plugins_storagepluginarray', src: this.server.name, dst: 'plugins' }, this.serverFetchTimeout)).result.plugins || [];
1497
+ const pluginsPaths = plugins.map((p) => path.join(this.matterbridge.matterbridgeDirectory, p.name + '.config.json'));
1498
+ this.zip('zip', path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), pluginsPaths, '');
1499
1499
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1500
1500
  }
1501
1501
  else if (data.method === '/api/unregister') {
@@ -2120,4 +2120,38 @@ export class Frontend extends EventEmitter {
2120
2120
  }
2121
2121
  });
2122
2122
  }
2123
+ spawn(command, packageName) {
2124
+ this.server.request({
2125
+ type: 'manager_run',
2126
+ src: 'frontend',
2127
+ dst: 'manager',
2128
+ params: {
2129
+ name: 'SpawnCommand',
2130
+ workerData: {
2131
+ threadName: 'SpawnCommand',
2132
+ command: 'npm',
2133
+ args: [command, '-g', packageName, '--omit=dev', '--verbose'],
2134
+ packageCommand: command,
2135
+ packageName: packageName,
2136
+ },
2137
+ },
2138
+ });
2139
+ }
2140
+ zip(command, archivePath, sourcePaths, destinationPath) {
2141
+ this.server.request({
2142
+ type: 'manager_run',
2143
+ src: 'frontend',
2144
+ dst: 'manager',
2145
+ params: {
2146
+ name: 'ArchiveCommand',
2147
+ workerData: {
2148
+ threadName: 'ArchiveCommand',
2149
+ command,
2150
+ archivePath,
2151
+ sourcePaths,
2152
+ destinationPath,
2153
+ },
2154
+ },
2155
+ });
2156
+ }
2123
2157
  }
@@ -238,6 +238,42 @@ export class Matterbridge extends EventEmitter {
238
238
  case 'matterbridge_shared':
239
239
  this.server.respond({ ...msg, result: { data: this.getSharedMatterbridge(), success: true } });
240
240
  break;
241
+ case 'matterbridge_start_plugin_server':
242
+ {
243
+ const plugin = this.plugins.get(msg.params.pluginName);
244
+ if (plugin && plugin.serverNode)
245
+ await this.startServerNode(plugin.serverNode);
246
+ this.server.respond({ ...msg, result: { success: true } });
247
+ }
248
+ break;
249
+ case 'matterbridge_stop_plugin_server':
250
+ {
251
+ const plugin = this.plugins.get(msg.params.pluginName);
252
+ if (plugin && plugin.serverNode)
253
+ await this.stopServerNode(plugin.serverNode);
254
+ if (plugin && plugin.serverNode)
255
+ plugin.serverNode = undefined;
256
+ this.server.respond({ ...msg, result: { success: true } });
257
+ }
258
+ break;
259
+ case 'matterbridge_start_device_server':
260
+ {
261
+ const device = this.devices.get(msg.params.deviceUniqueId);
262
+ if (device && device.serverNode)
263
+ await this.startServerNode(device.serverNode);
264
+ this.server.respond({ ...msg, result: { success: true } });
265
+ }
266
+ break;
267
+ case 'matterbridge_stop_device_server':
268
+ {
269
+ const device = this.devices.get(msg.params.deviceUniqueId);
270
+ if (device && device.serverNode)
271
+ await this.stopServerNode(device.serverNode);
272
+ if (device && device.serverNode)
273
+ device.serverNode = undefined;
274
+ this.server.respond({ ...msg, result: { success: true } });
275
+ }
276
+ break;
241
277
  default:
242
278
  if (this.verbose)
243
279
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
@@ -132,10 +132,10 @@ export class PluginManager extends EventEmitter {
132
132
  {
133
133
  const plugin = await this.remove(msg.params.nameOrPath);
134
134
  if (plugin) {
135
- this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
135
+ this.server.respond({ ...msg, result: { success: true } });
136
136
  }
137
137
  else {
138
- this.server.respond({ ...msg, result: { plugin } });
138
+ this.server.respond({ ...msg, result: { success: false } });
139
139
  }
140
140
  }
141
141
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.6.2-dev-20260314-f95be8a",
3
+ "version": "3.6.2-dev-20260316-0b03ae0",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -112,7 +112,7 @@
112
112
  }
113
113
  },
114
114
  "engines": {
115
- "node": ">=20.0.0 <21.0.0 || >=22.0.0 <23.0.0 || >=24.0.0 <25.0.0"
115
+ "node": ">=20.19.0 <21.0.0 || >=22.13.0 <23.0.0 || >=24.0.0 <25.0.0"
116
116
  },
117
117
  "files": [
118
118
  "bin",
@@ -122,13 +122,11 @@
122
122
  ],
123
123
  "dependencies": {
124
124
  "@matter/main": "0.16.10",
125
- "@matterbridge/dgram": "3.6.2-dev-20260314-f95be8a",
126
- "@matterbridge/thread": "3.6.2-dev-20260314-f95be8a",
127
- "@matterbridge/types": "3.6.2-dev-20260314-f95be8a",
128
- "@matterbridge/utils": "3.6.2-dev-20260314-f95be8a",
129
- "archiver": "7.0.1",
125
+ "@matterbridge/dgram": "3.6.2-dev-20260316-0b03ae0",
126
+ "@matterbridge/thread": "3.6.2-dev-20260316-0b03ae0",
127
+ "@matterbridge/types": "3.6.2-dev-20260316-0b03ae0",
128
+ "@matterbridge/utils": "3.6.2-dev-20260316-0b03ae0",
130
129
  "express": "5.2.1",
131
- "glob": "13.0.6",
132
130
  "multer": "2.1.1",
133
131
  "node-ansi-logger": "3.2.0",
134
132
  "node-persist-manager": "2.0.1",