matterbridge 3.0.1-dev-20250501-4f463f9 → 3.0.1-dev-20250502-f374923

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.
package/dist/frontend.js CHANGED
@@ -8,7 +8,7 @@ import express from 'express';
8
8
  import WebSocket, { WebSocketServer } from 'ws';
9
9
  import multer from 'multer';
10
10
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
11
- import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString } from './utils/export.js';
11
+ import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/export.js';
12
12
  import { plg } from './matterbridgeTypes.js';
13
13
  import { hasParameter } from './utils/export.js';
14
14
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
@@ -21,6 +21,7 @@ export const WS_ID_UPTIME_UPDATE = 5;
21
21
  export const WS_ID_SNACKBAR = 6;
22
22
  export const WS_ID_UPDATE_NEEDED = 7;
23
23
  export const WS_ID_STATEUPDATE = 8;
24
+ export const WS_ID_CLOSE_SNACKBAR = 9;
24
25
  export const WS_ID_SHELLY_SYS_UPDATE = 100;
25
26
  export const WS_ID_SHELLY_MAIN_UPDATE = 101;
26
27
  export class Frontend {
@@ -257,16 +258,6 @@ export class Frontend {
257
258
  };
258
259
  res.status(200).json(memoryReport);
259
260
  });
260
- this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
261
- const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
262
- if (pairingCodes) {
263
- const { manualPairingCode, qrPairingCode } = pairingCodes;
264
- res.json({ manualPairingCode, qrPairingCode: 'https://project-chip.github.io/connectedhomeip/qrcode.html?data=' + qrPairingCode });
265
- }
266
- else {
267
- res.status(500).json({ error: 'Failed to generate pairing codes' });
268
- }
269
- });
270
261
  this.expressApp.get('/api/settings', express.json(), async (req, res) => {
271
262
  this.log.debug('The frontend sent /api/settings');
272
263
  res.json(await this.getApiSettings());
@@ -275,100 +266,11 @@ export class Frontend {
275
266
  this.log.debug('The frontend sent /api/plugins');
276
267
  res.json(this.getBaseRegisteredPlugins());
277
268
  });
278
- this.expressApp.get('/api/devices', (req, res) => {
269
+ this.expressApp.get('/api/devices', async (req, res) => {
279
270
  this.log.debug('The frontend sent /api/devices');
280
- const devices = [];
281
- this.matterbridge.devices.forEach(async (device) => {
282
- if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
283
- return;
284
- const cluster = this.getClusterTextFromDevice(device);
285
- devices.push({
286
- pluginName: device.plugin,
287
- type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
288
- endpoint: device.number,
289
- name: device.deviceName,
290
- serial: device.serialNumber,
291
- productUrl: device.productUrl,
292
- configUrl: device.configUrl,
293
- uniqueId: device.uniqueId,
294
- reachable: this.getReachability(device),
295
- cluster: cluster,
296
- });
297
- });
271
+ const devices = await this.getDevices();
298
272
  res.json(devices);
299
273
  });
300
- this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
301
- const selectedPluginName = req.params.selectedPluginName;
302
- const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
303
- this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
304
- if (selectedPluginName === 'none') {
305
- res.json([]);
306
- return;
307
- }
308
- const data = [];
309
- this.matterbridge.devices.forEach(async (device) => {
310
- const pluginName = device.plugin;
311
- if (pluginName === selectedPluginName && device.number === selectedDeviceEndpoint) {
312
- const endpointServer = EndpointServer.forEndpoint(device);
313
- const clusterServers = endpointServer.getAllClusterServers();
314
- clusterServers.forEach((clusterServer) => {
315
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
316
- if (clusterServer.name === 'EveHistory')
317
- return;
318
- let attributeValue;
319
- try {
320
- if (typeof value.getLocal() === 'object')
321
- attributeValue = stringify(value.getLocal());
322
- else
323
- attributeValue = value.getLocal().toString();
324
- }
325
- catch (error) {
326
- attributeValue = 'Fabric-Scoped';
327
- this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
328
- }
329
- data.push({
330
- endpoint: device.number ? device.number.toString() : '...',
331
- clusterName: clusterServer.name,
332
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
333
- attributeName: key,
334
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
335
- attributeValue,
336
- });
337
- });
338
- });
339
- endpointServer.getChildEndpoints().forEach((childEndpoint) => {
340
- const name = childEndpoint.name;
341
- const clusterServers = childEndpoint.getAllClusterServers();
342
- clusterServers.forEach((clusterServer) => {
343
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
344
- if (clusterServer.name === 'EveHistory')
345
- return;
346
- let attributeValue;
347
- try {
348
- if (typeof value.getLocal() === 'object')
349
- attributeValue = stringify(value.getLocal());
350
- else
351
- attributeValue = value.getLocal().toString();
352
- }
353
- catch (error) {
354
- attributeValue = 'Fabric-Scoped';
355
- this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
356
- }
357
- data.push({
358
- endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
359
- clusterName: clusterServer.name,
360
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
361
- attributeName: key,
362
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
363
- attributeValue,
364
- });
365
- });
366
- });
367
- });
368
- }
369
- });
370
- res.json(data);
371
- });
372
274
  this.expressApp.get('/api/view-mblog', async (req, res) => {
373
275
  this.log.debug('The frontend sent /api/view-mblog');
374
276
  try {
@@ -511,316 +413,6 @@ export class Frontend {
511
413
  }
512
414
  });
513
415
  });
514
- this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
515
- const command = req.params.command;
516
- let param = req.params.param;
517
- this.log.debug(`The frontend sent /api/command/${command}/${param}`);
518
- if (!command) {
519
- res.status(400).json({ error: 'No command provided' });
520
- return;
521
- }
522
- this.log.debug(`Received frontend command: ${command}:${param}`);
523
- if (command === 'setpassword') {
524
- const password = param.slice(1, -1);
525
- this.log.debug('setpassword', param, password);
526
- await this.matterbridge.nodeContext?.set('password', password);
527
- res.json({ message: 'Command received' });
528
- return;
529
- }
530
- if (command === 'setbridgemode') {
531
- this.log.debug(`setbridgemode: ${param}`);
532
- this.wssSendRestartRequired();
533
- await this.matterbridge.nodeContext?.set('bridgeMode', param);
534
- res.json({ message: 'Command received' });
535
- return;
536
- }
537
- if (command === 'backup') {
538
- this.log.notice(`Prepairing the backup...`);
539
- await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
540
- this.log.notice(`Backup ready to be downloaded.`);
541
- this.wssSendSnackbarMessage('Backup ready to be downloaded', 10);
542
- res.json({ message: 'Command received' });
543
- return;
544
- }
545
- if (command === 'setmbloglevel') {
546
- this.log.debug('Matterbridge log level:', param);
547
- if (param === 'Debug') {
548
- this.log.logLevel = "debug";
549
- }
550
- else if (param === 'Info') {
551
- this.log.logLevel = "info";
552
- }
553
- else if (param === 'Notice') {
554
- this.log.logLevel = "notice";
555
- }
556
- else if (param === 'Warn') {
557
- this.log.logLevel = "warn";
558
- }
559
- else if (param === 'Error') {
560
- this.log.logLevel = "error";
561
- }
562
- else if (param === 'Fatal') {
563
- this.log.logLevel = "fatal";
564
- }
565
- await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
566
- await this.matterbridge.setLogLevel(this.log.logLevel);
567
- res.json({ message: 'Command received' });
568
- return;
569
- }
570
- if (command === 'setmjloglevel') {
571
- this.log.debug('Matter.js log level:', param);
572
- if (param === 'Debug') {
573
- Logger.defaultLogLevel = MatterLogLevel.DEBUG;
574
- }
575
- else if (param === 'Info') {
576
- Logger.defaultLogLevel = MatterLogLevel.INFO;
577
- }
578
- else if (param === 'Notice') {
579
- Logger.defaultLogLevel = MatterLogLevel.NOTICE;
580
- }
581
- else if (param === 'Warn') {
582
- Logger.defaultLogLevel = MatterLogLevel.WARN;
583
- }
584
- else if (param === 'Error') {
585
- Logger.defaultLogLevel = MatterLogLevel.ERROR;
586
- }
587
- else if (param === 'Fatal') {
588
- Logger.defaultLogLevel = MatterLogLevel.FATAL;
589
- }
590
- await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.defaultLogLevel);
591
- res.json({ message: 'Command received' });
592
- return;
593
- }
594
- if (command === 'setmdnsinterface') {
595
- if (param === 'json' && isValidString(req.body.value)) {
596
- this.matterbridge.matterbridgeInformation.mattermdnsinterface = req.body.value;
597
- this.log.debug(`Matter.js mdns interface: ${req.body.value === '' ? 'all interfaces' : req.body.value}`);
598
- await this.matterbridge.nodeContext?.set('mattermdnsinterface', req.body.value);
599
- }
600
- res.json({ message: 'Command received' });
601
- return;
602
- }
603
- if (command === 'setipv4address') {
604
- if (param === 'json' && isValidString(req.body.value)) {
605
- this.log.debug(`Matter.js ipv4 address: ${req.body.value === '' ? 'all ipv4 addresses' : req.body.value}`);
606
- this.matterbridge.matterbridgeInformation.matteripv4address = req.body.value;
607
- await this.matterbridge.nodeContext?.set('matteripv4address', req.body.value);
608
- }
609
- res.json({ message: 'Command received' });
610
- return;
611
- }
612
- if (command === 'setipv6address') {
613
- if (param === 'json' && isValidString(req.body.value)) {
614
- this.log.debug(`Matter.js ipv6 address: ${req.body.value === '' ? 'all ipv6 addresses' : req.body.value}`);
615
- this.matterbridge.matterbridgeInformation.matteripv6address = req.body.value;
616
- await this.matterbridge.nodeContext?.set('matteripv6address', req.body.value);
617
- }
618
- res.json({ message: 'Command received' });
619
- return;
620
- }
621
- if (command === 'setmatterport') {
622
- const port = Math.min(Math.max(parseInt(req.body.value), 5540), 5560);
623
- this.matterbridge.matterbridgeInformation.matterPort = port;
624
- this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
625
- await this.matterbridge.nodeContext?.set('matterport', port);
626
- res.json({ message: 'Command received' });
627
- return;
628
- }
629
- if (command === 'setmatterdiscriminator') {
630
- const discriminator = Math.min(Math.max(parseInt(req.body.value), 1000), 4095);
631
- this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
632
- this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
633
- await this.matterbridge.nodeContext?.set('matterdiscriminator', discriminator);
634
- res.json({ message: 'Command received' });
635
- return;
636
- }
637
- if (command === 'setmatterpasscode') {
638
- const passcode = Math.min(Math.max(parseInt(req.body.value), 10000000), 90000000);
639
- this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
640
- this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
641
- await this.matterbridge.nodeContext?.set('matterpasscode', passcode);
642
- res.json({ message: 'Command received' });
643
- return;
644
- }
645
- if (command === 'setmblogfile') {
646
- this.log.debug('Matterbridge file log:', param);
647
- this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
648
- await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
649
- if (param === 'true')
650
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug", true);
651
- else
652
- AnsiLogger.setGlobalLogfile(undefined);
653
- res.json({ message: 'Command received' });
654
- return;
655
- }
656
- if (command === 'setmjlogfile') {
657
- this.log.debug('Matter file log:', param);
658
- this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
659
- await this.matterbridge.nodeContext?.set('matterFileLog', param === 'true');
660
- if (param === 'true') {
661
- try {
662
- Logger.addLogger('matterfilelogger', await this.matterbridge.createMatterFileLogger(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), true), {
663
- defaultLogLevel: MatterLogLevel.DEBUG,
664
- logFormat: MatterLogFormat.PLAIN,
665
- });
666
- }
667
- catch (error) {
668
- this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
669
- }
670
- }
671
- else {
672
- try {
673
- Logger.removeLogger('matterfilelogger');
674
- }
675
- catch (error) {
676
- this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
677
- }
678
- }
679
- res.json({ message: 'Command received' });
680
- return;
681
- }
682
- if (command === 'unregister') {
683
- await this.matterbridge.unregisterAndShutdownProcess();
684
- res.json({ message: 'Command received' });
685
- return;
686
- }
687
- if (command === 'reset') {
688
- await this.matterbridge.shutdownProcessAndReset();
689
- res.json({ message: 'Command received' });
690
- return;
691
- }
692
- if (command === 'factoryreset') {
693
- await this.matterbridge.shutdownProcessAndFactoryReset();
694
- res.json({ message: 'Command received' });
695
- return;
696
- }
697
- if (command === 'shutdown') {
698
- await this.matterbridge.shutdownProcess();
699
- res.json({ message: 'Command received' });
700
- return;
701
- }
702
- if (command === 'restart') {
703
- await this.matterbridge.restartProcess();
704
- res.json({ message: 'Command received' });
705
- return;
706
- }
707
- if (command === 'update') {
708
- await this.matterbridge.updateProcess();
709
- this.wssSendRestartRequired();
710
- res.json({ message: 'Command received' });
711
- return;
712
- }
713
- if (command === 'saveconfig') {
714
- param = param.replace(/\*/g, '\\');
715
- this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
716
- if (!this.matterbridge.plugins.has(param)) {
717
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
718
- }
719
- else {
720
- const plugin = this.matterbridge.plugins.get(param);
721
- if (!plugin)
722
- return;
723
- this.matterbridge.plugins.saveConfigFromJson(plugin, req.body);
724
- this.wssSendSnackbarMessage(`Saved config for plugin ${param}`);
725
- this.wssSendRestartRequired();
726
- }
727
- res.json({ message: 'Command received' });
728
- return;
729
- }
730
- if (command === 'installplugin') {
731
- param = param.replace(/\*/g, '\\');
732
- this.log.info(`Installing plugin ${plg}${param}${nf}...`);
733
- this.wssSendSnackbarMessage(`Installing package ${param}. Please wait...`);
734
- try {
735
- await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
736
- this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
737
- this.wssSendSnackbarMessage(`Installed package ${param}`, 10, 'success');
738
- }
739
- catch (error) {
740
- this.log.error(`Error installing plugin ${plg}${param}${er}`);
741
- this.wssSendSnackbarMessage(`Package ${param} not installed`, 10, 'error');
742
- }
743
- this.wssSendRestartRequired();
744
- param = param.split('@')[0];
745
- if (param === 'matterbridge') {
746
- res.json({ message: 'Command received' });
747
- return;
748
- }
749
- }
750
- if (command === 'addplugin' || command === 'installplugin') {
751
- param = param.replace(/\*/g, '\\');
752
- const plugin = await this.matterbridge.plugins.add(param);
753
- if (plugin) {
754
- this.wssSendSnackbarMessage(`Added plugin ${param}`);
755
- if (this.matterbridge.bridgeMode === 'childbridge') {
756
- this.matterbridge.createDynamicPlugin(plugin, true);
757
- }
758
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
759
- this.wssSendRefreshRequired('plugins');
760
- });
761
- }
762
- res.json({ message: 'Command received' });
763
- return;
764
- }
765
- if (command === 'removeplugin') {
766
- if (!this.matterbridge.plugins.has(param)) {
767
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
768
- }
769
- else {
770
- const plugin = this.matterbridge.plugins.get(param);
771
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
772
- await this.matterbridge.plugins.remove(param);
773
- this.wssSendSnackbarMessage(`Removed plugin ${param}`);
774
- this.wssSendRefreshRequired('plugins');
775
- }
776
- res.json({ message: 'Command received' });
777
- return;
778
- }
779
- if (command === 'enableplugin') {
780
- if (!this.matterbridge.plugins.has(param)) {
781
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
782
- }
783
- else {
784
- const plugin = this.matterbridge.plugins.get(param);
785
- if (plugin && !plugin.enabled) {
786
- plugin.locked = undefined;
787
- plugin.error = undefined;
788
- plugin.loaded = undefined;
789
- plugin.started = undefined;
790
- plugin.configured = undefined;
791
- plugin.platform = undefined;
792
- plugin.registeredDevices = undefined;
793
- plugin.addedDevices = undefined;
794
- await this.matterbridge.plugins.enable(param);
795
- this.wssSendSnackbarMessage(`Enabled plugin ${param}`);
796
- if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
797
- this.matterbridge.createDynamicPlugin(plugin, true);
798
- }
799
- this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
800
- this.wssSendRefreshRequired('plugins');
801
- });
802
- }
803
- }
804
- res.json({ message: 'Command received' });
805
- return;
806
- }
807
- if (command === 'disableplugin') {
808
- if (!this.matterbridge.plugins.has(param)) {
809
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
810
- }
811
- else {
812
- const plugin = this.matterbridge.plugins.get(param);
813
- if (plugin && plugin.enabled) {
814
- await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
815
- await this.matterbridge.plugins.disable(param);
816
- this.wssSendSnackbarMessage(`Disabled plugin ${param}`);
817
- this.wssSendRefreshRequired('plugins');
818
- }
819
- }
820
- res.json({ message: 'Command received' });
821
- return;
822
- }
823
- });
824
416
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
825
417
  const { filename } = req.body;
826
418
  const file = req.file;
@@ -829,7 +421,7 @@ export class Frontend {
829
421
  res.status(400).send('Invalid request: file and filename are required');
830
422
  return;
831
423
  }
832
- this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`);
424
+ this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
833
425
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
834
426
  try {
835
427
  await fs.rename(file.path, filePath);
@@ -837,6 +429,7 @@ export class Frontend {
837
429
  if (filename.endsWith('.tgz')) {
838
430
  await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
839
431
  this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
432
+ this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
840
433
  this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
841
434
  this.wssSendRestartRequired();
842
435
  res.send(`Plugin package ${filename} uploaded and installed successfully`);
@@ -846,6 +439,7 @@ export class Frontend {
846
439
  }
847
440
  catch (err) {
848
441
  this.log.error(`Error uploading or installing plugin package file ${plg}${filename}${er}:`, err);
442
+ this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
849
443
  this.wssSendSnackbarMessage(`Error uploading or installing plugin package ${filename}`, 10, 'error');
850
444
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
851
445
  }
@@ -1133,6 +727,7 @@ export class Frontend {
1133
727
  started: plugin.started,
1134
728
  configured: plugin.configured,
1135
729
  paired: plugin.paired,
730
+ restartRequired: plugin.restartRequired,
1136
731
  fabricInformations: plugin.fabricInformations,
1137
732
  sessionInformations: plugin.sessionInformations,
1138
733
  registeredDevices: plugin.registeredDevices,
@@ -1147,6 +742,30 @@ export class Frontend {
1147
742
  }
1148
743
  return baseRegisteredPlugins;
1149
744
  }
745
+ async getDevices(pluginName) {
746
+ const devices = [];
747
+ this.matterbridge.devices.forEach(async (device) => {
748
+ if (pluginName && pluginName !== device.plugin)
749
+ return;
750
+ if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
751
+ return;
752
+ const cluster = this.getClusterTextFromDevice(device);
753
+ devices.push({
754
+ pluginName: device.plugin,
755
+ type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
756
+ endpoint: device.number,
757
+ name: device.deviceName,
758
+ serial: device.serialNumber,
759
+ productUrl: device.productUrl,
760
+ configUrl: device.configUrl,
761
+ uniqueId: device.uniqueId,
762
+ reachable: this.getReachability(device),
763
+ powerSource: this.getPowerSource(device),
764
+ cluster: cluster,
765
+ });
766
+ });
767
+ return devices;
768
+ }
1150
769
  async wsMessageHandler(client, message) {
1151
770
  let data;
1152
771
  try {
@@ -1180,97 +799,230 @@ export class Frontend {
1180
799
  }
1181
800
  }
1182
801
  else if (data.method === '/api/install') {
1183
- if (!isValidString(data.params.packageName, 10)) {
1184
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/install' }));
802
+ if (!isValidString(data.params.packageName, 10) || !isValidBoolean(data.params.restart)) {
803
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/install' }));
1185
804
  return;
1186
805
  }
1187
- this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}`);
806
+ this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
1188
807
  this.matterbridge
1189
808
  .spawnCommand('npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'])
1190
809
  .then((response) => {
1191
810
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
1192
- this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`);
1193
- if (data.params.restart !== true) {
1194
- this.wssSendSnackbarMessage(`Restart required`, 0);
811
+ this.wssSendCloseSnackbarMessage(`Installing package ${data.params.packageName}...`);
812
+ this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
813
+ if (data.params.restart === false) {
814
+ data.params.packageName = data.params.packageName.replace(/@.*$/, '');
815
+ this.matterbridge.plugins.add(data.params.packageName).then((plugin) => {
816
+ if (plugin) {
817
+ this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
818
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
819
+ this.wssSendSnackbarMessage(`Started plugin ${data.params.pluginNameOrPath}`, 5, 'success');
820
+ this.wssSendRefreshRequired('plugins');
821
+ });
822
+ }
823
+ else {
824
+ this.wssSendSnackbarMessage(`Restart required`, 0);
825
+ this.wssSendRefreshRequired('plugins');
826
+ this.wssSendRestartRequired();
827
+ }
828
+ });
1195
829
  }
1196
830
  else {
1197
831
  if (this.matterbridge.restartMode !== '') {
1198
832
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1199
833
  this.matterbridge.shutdownProcess();
1200
834
  }
1201
- else
835
+ else {
1202
836
  this.wssSendSnackbarMessage(`Restart required`, 0);
837
+ this.wssSendRestartRequired();
838
+ }
1203
839
  }
1204
840
  })
1205
841
  .catch((error) => {
1206
842
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error }));
1207
- this.wssSendSnackbarMessage(`Package ${data.params.packageName} not installed`);
843
+ this.wssSendCloseSnackbarMessage(`Installing package ${data.params.packageName}...`);
844
+ this.wssSendSnackbarMessage(`Package ${data.params.packageName} not installed`, 10, 'error');
1208
845
  });
1209
- return;
1210
846
  }
1211
847
  else if (data.method === '/api/uninstall') {
1212
848
  if (!isValidString(data.params.packageName, 10)) {
1213
849
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
1214
850
  return;
1215
851
  }
1216
- this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}`);
852
+ const plugin = this.matterbridge.plugins.get(data.params.packageName);
853
+ if (plugin) {
854
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
855
+ await this.matterbridge.plugins.remove(data.params.packageName);
856
+ this.wssSendSnackbarMessage(`Removed plugin ${data.params.packageName}`, 5, 'success');
857
+ this.wssSendRefreshRequired('plugins');
858
+ this.wssSendRefreshRequired('devices');
859
+ }
860
+ this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
1217
861
  this.matterbridge
1218
862
  .spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
1219
863
  .then((response) => {
1220
864
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
865
+ this.wssSendCloseSnackbarMessage(`Uninstalling package ${data.params.packageName}...`);
866
+ this.wssSendSnackbarMessage(`Uninstalled package ${data.params.packageName}`, 5, 'success');
1221
867
  })
1222
868
  .catch((error) => {
1223
869
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error }));
1224
- this.wssSendSnackbarMessage(`Uninstalled package ${data.params.packageName}`);
870
+ this.wssSendCloseSnackbarMessage(`Uninstalling package ${data.params.packageName}...`);
871
+ this.wssSendSnackbarMessage(`Package ${data.params.packageName} not uninstalled`, 10, 'error');
1225
872
  this.wssSendSnackbarMessage(`Restart required`, 0);
1226
873
  });
1227
- return;
874
+ }
875
+ else if (data.method === '/api/addplugin') {
876
+ if (!isValidString(data.params.pluginNameOrPath, 10)) {
877
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginNameOrPath in /api/addplugin' }));
878
+ return;
879
+ }
880
+ data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
881
+ if (this.matterbridge.plugins.has(data.params.pluginNameOrPath)) {
882
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} already added` }));
883
+ this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} already added`, 10, 'warning');
884
+ return;
885
+ }
886
+ const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
887
+ if (plugin) {
888
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: plugin.name }));
889
+ this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
890
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
891
+ this.wssSendRefreshRequired('plugins');
892
+ this.wssSendRefreshRequired('devices');
893
+ this.wssSendSnackbarMessage(`Started plugin ${data.params.pluginNameOrPath}`, 5, 'success');
894
+ });
895
+ }
896
+ else {
897
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` }));
898
+ this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
899
+ }
900
+ }
901
+ else if (data.method === '/api/removeplugin') {
902
+ if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
903
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/removeplugin' }));
904
+ return;
905
+ }
906
+ const plugin = this.matterbridge.plugins.get(data.params.pluginName);
907
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
908
+ await this.matterbridge.plugins.remove(data.params.pluginName);
909
+ this.wssSendSnackbarMessage(`Removed plugin ${data.params.pluginName}`, 5, 'success');
910
+ this.wssSendRefreshRequired('plugins');
911
+ this.wssSendRefreshRequired('devices');
912
+ }
913
+ else if (data.method === '/api/enableplugin') {
914
+ if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
915
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/enableplugin' }));
916
+ return;
917
+ }
918
+ const plugin = this.matterbridge.plugins.get(data.params.pluginName);
919
+ if (plugin && !plugin.enabled) {
920
+ plugin.locked = undefined;
921
+ plugin.error = undefined;
922
+ plugin.loaded = undefined;
923
+ plugin.started = undefined;
924
+ plugin.configured = undefined;
925
+ plugin.platform = undefined;
926
+ plugin.registeredDevices = undefined;
927
+ plugin.addedDevices = undefined;
928
+ await this.matterbridge.plugins.enable(data.params.pluginName);
929
+ this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
930
+ this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
931
+ this.wssSendRefreshRequired('plugins');
932
+ this.wssSendRefreshRequired('devices');
933
+ this.wssSendSnackbarMessage(`Started plugin ${data.params.pluginName}`, 5, 'success');
934
+ });
935
+ }
936
+ }
937
+ else if (data.method === '/api/disableplugin') {
938
+ if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
939
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/disableplugin' }));
940
+ return;
941
+ }
942
+ const plugin = this.matterbridge.plugins.get(data.params.pluginName);
943
+ await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
944
+ await this.matterbridge.plugins.disable(data.params.pluginName);
945
+ this.wssSendSnackbarMessage(`Disabled plugin ${data.params.pluginName}`, 5, 'success');
946
+ this.wssSendRefreshRequired('plugins');
947
+ this.wssSendRefreshRequired('devices');
948
+ }
949
+ else if (data.method === '/api/savepluginconfig') {
950
+ if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
951
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/savepluginconfig' }));
952
+ return;
953
+ }
954
+ if (!isValidObject(data.params.formData, 5)) {
955
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter formData in /api/savepluginconfig' }));
956
+ return;
957
+ }
958
+ this.log.info(`Saving config for plugin ${plg}${data.params.pluginName}${nf}...`);
959
+ const plugin = this.matterbridge.plugins.get(data.params.pluginName);
960
+ if (!plugin) {
961
+ this.log.warn(`Plugin ${plg}${data.params.pluginName}${wr} not found in matterbridge`);
962
+ }
963
+ else {
964
+ this.matterbridge.plugins.saveConfigFromJson(plugin, data.params.formData);
965
+ this.wssSendSnackbarMessage(`Saved config for plugin ${data.params.pluginName}`);
966
+ this.wssSendRefreshRequired('plugins');
967
+ this.wssSendRestartRequired();
968
+ }
1228
969
  }
1229
970
  else if (data.method === '/api/shellysysupdate') {
1230
971
  const { triggerShellySysUpdate } = await import('./shelly.js');
1231
972
  triggerShellySysUpdate(this.matterbridge);
1232
- return;
1233
973
  }
1234
974
  else if (data.method === '/api/shellymainupdate') {
1235
975
  const { triggerShellyMainUpdate } = await import('./shelly.js');
1236
976
  triggerShellyMainUpdate(this.matterbridge);
1237
- return;
1238
977
  }
1239
978
  else if (data.method === '/api/shellycreatesystemlog') {
1240
979
  const { createShellySystemLog } = await import('./shelly.js');
1241
980
  createShellySystemLog(this.matterbridge);
1242
- return;
1243
981
  }
1244
982
  else if (data.method === '/api/shellynetconfig') {
1245
983
  this.log.debug('/api/shellynetconfig:', data.params);
1246
984
  const { triggerShellyChangeIp: triggerShellyChangeNet } = await import('./shelly.js');
1247
985
  triggerShellyChangeNet(this.matterbridge, data.params);
1248
- return;
1249
986
  }
1250
987
  else if (data.method === '/api/softreset') {
1251
988
  const { triggerShellySoftReset } = await import('./shelly.js');
1252
989
  triggerShellySoftReset(this.matterbridge);
1253
- return;
1254
990
  }
1255
991
  else if (data.method === '/api/hardreset') {
1256
992
  const { triggerShellyHardReset } = await import('./shelly.js');
1257
993
  triggerShellyHardReset(this.matterbridge);
1258
- return;
1259
994
  }
1260
995
  else if (data.method === '/api/reboot') {
1261
996
  const { triggerShellyReboot } = await import('./shelly.js');
1262
997
  triggerShellyReboot(this.matterbridge);
1263
- return;
1264
998
  }
1265
999
  else if (data.method === '/api/restart') {
1266
1000
  this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
1267
1001
  await this.matterbridge.restartProcess();
1268
- return;
1269
1002
  }
1270
1003
  else if (data.method === '/api/shutdown') {
1271
1004
  this.wssSendSnackbarMessage(`Shutting down matterbridge...`, 0);
1272
1005
  await this.matterbridge.shutdownProcess();
1273
- return;
1006
+ }
1007
+ else if (data.method === '/api/create-backup') {
1008
+ this.wssSendSnackbarMessage('Creating backup...', 0);
1009
+ this.log.notice(`Creating the backup...`);
1010
+ await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
1011
+ this.log.notice(`Backup ready to be downloaded.`);
1012
+ this.wssSendCloseSnackbarMessage('Creating backup...');
1013
+ this.wssSendSnackbarMessage('Backup ready to be downloaded', 10);
1014
+ }
1015
+ else if (data.method === '/api/unregister') {
1016
+ this.wssSendSnackbarMessage('Uregistering all bridged devices...', 10);
1017
+ await this.matterbridge.unregisterAndShutdownProcess();
1018
+ }
1019
+ else if (data.method === '/api/reset') {
1020
+ this.wssSendSnackbarMessage('Resetting matterbridge commissioning...', 10);
1021
+ await this.matterbridge.shutdownProcessAndReset();
1022
+ }
1023
+ else if (data.method === '/api/factoryreset') {
1024
+ this.wssSendSnackbarMessage('Factory reset of matterbridge...', 10);
1025
+ await this.matterbridge.shutdownProcessAndFactoryReset();
1274
1026
  }
1275
1027
  else if (data.method === '/api/advertise') {
1276
1028
  const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
@@ -1280,7 +1032,6 @@ export class Frontend {
1280
1032
  this.wssSendRefreshRequired('matterbridgeAdvertise');
1281
1033
  this.wssSendSnackbarMessage(`Started fabrics share`, 0);
1282
1034
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes }));
1283
- return;
1284
1035
  }
1285
1036
  else if (data.method === '/api/stopadvertise') {
1286
1037
  await this.matterbridge.stopAdvertiseServerNode(this.matterbridge.serverNode);
@@ -1288,41 +1039,17 @@ export class Frontend {
1288
1039
  this.wssSendRefreshRequired('matterbridgeAdvertise');
1289
1040
  this.wssSendSnackbarMessage(`Stopped fabrics share`, 0);
1290
1041
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src }));
1291
- return;
1292
1042
  }
1293
1043
  else if (data.method === '/api/settings') {
1294
1044
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: await this.getApiSettings() }));
1295
- return;
1296
1045
  }
1297
1046
  else if (data.method === '/api/plugins') {
1298
1047
  const response = this.getBaseRegisteredPlugins();
1299
1048
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
1300
- return;
1301
1049
  }
1302
1050
  else if (data.method === '/api/devices') {
1303
- const devices = [];
1304
- this.matterbridge.devices.forEach(async (device) => {
1305
- if (data.params.pluginName && data.params.pluginName !== device.plugin)
1306
- return;
1307
- if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1308
- return;
1309
- const cluster = this.getClusterTextFromDevice(device);
1310
- devices.push({
1311
- pluginName: device.plugin,
1312
- type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
1313
- endpoint: device.number,
1314
- name: device.deviceName,
1315
- serial: device.serialNumber,
1316
- productUrl: device.productUrl,
1317
- configUrl: device.configUrl,
1318
- uniqueId: device.uniqueId,
1319
- reachable: this.getReachability(device),
1320
- powerSource: this.getPowerSource(device),
1321
- cluster: cluster,
1322
- });
1323
- });
1051
+ const devices = await this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1324
1052
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: devices }));
1325
- return;
1326
1053
  }
1327
1054
  else if (data.method === '/api/clusters') {
1328
1055
  if (!isValidString(data.params.plugin, 10)) {
@@ -1429,7 +1156,6 @@ export class Frontend {
1429
1156
  });
1430
1157
  });
1431
1158
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, deviceName, serialNumber, endpoint: data.params.endpoint, deviceTypes, response: clusters }));
1432
- return;
1433
1159
  }
1434
1160
  else if (data.method === '/api/select' || data.method === '/api/select/devices') {
1435
1161
  if (!isValidString(data.params.plugin, 10)) {
@@ -1443,7 +1169,6 @@ export class Frontend {
1443
1169
  }
1444
1170
  const selectDeviceValues = plugin.platform?.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1445
1171
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectDeviceValues }));
1446
- return;
1447
1172
  }
1448
1173
  else if (data.method === '/api/select/entities') {
1449
1174
  if (!isValidString(data.params.plugin, 10)) {
@@ -1457,7 +1182,6 @@ export class Frontend {
1457
1182
  }
1458
1183
  const selectEntityValues = plugin.platform?.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1459
1184
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, response: selectEntityValues }));
1460
- return;
1461
1185
  }
1462
1186
  else if (data.method === '/api/action') {
1463
1187
  if (!isValidString(data.params.plugin, 5) || !isValidString(data.params.action, 1)) {
@@ -1474,6 +1198,185 @@ export class Frontend {
1474
1198
  this.log.error(`Error in plugin ${plugin.name} action ${data.params.action}: ${error}`);
1475
1199
  });
1476
1200
  }
1201
+ else if (data.method === '/api/config') {
1202
+ if (!isValidString(data.params.name, 5) || data.params.value === undefined) {
1203
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/config' }));
1204
+ return;
1205
+ }
1206
+ this.log.debug(`Received /api/config name ${CYAN}${data.params.name}${db} value ${CYAN}${data.params.value}${db}`);
1207
+ this.log.fatal(`Received /api/config name ${CYAN}${data.params.name}${db} value(${typeof data.params.value}) ${CYAN}${data.params.value}${db}`);
1208
+ switch (data.params.name) {
1209
+ case 'setpassword':
1210
+ if (isValidString(data.params.value)) {
1211
+ await this.matterbridge.nodeContext?.set('password', data.params.value);
1212
+ }
1213
+ break;
1214
+ case 'setbridgemode':
1215
+ if (isValidString(data.params.value, 5)) {
1216
+ await this.matterbridge.nodeContext?.set('bridgeMode', data.params.value);
1217
+ this.wssSendRestartRequired();
1218
+ }
1219
+ break;
1220
+ case 'setmbloglevel':
1221
+ if (isValidString(data.params.value, 4)) {
1222
+ this.log.debug('Matterbridge logger level:', data.params.value);
1223
+ if (data.params.value === 'Debug') {
1224
+ await this.matterbridge.setLogLevel("debug");
1225
+ }
1226
+ else if (data.params.value === 'Info') {
1227
+ await this.matterbridge.setLogLevel("info");
1228
+ }
1229
+ else if (data.params.value === 'Notice') {
1230
+ await this.matterbridge.setLogLevel("notice");
1231
+ }
1232
+ else if (data.params.value === 'Warn') {
1233
+ await this.matterbridge.setLogLevel("warn");
1234
+ }
1235
+ else if (data.params.value === 'Error') {
1236
+ await this.matterbridge.setLogLevel("error");
1237
+ }
1238
+ else if (data.params.value === 'Fatal') {
1239
+ await this.matterbridge.setLogLevel("fatal");
1240
+ }
1241
+ await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
1242
+ }
1243
+ break;
1244
+ case 'setmblogfile':
1245
+ if (isValidBoolean(data.params.value)) {
1246
+ this.log.debug('Matterbridge file log:', data.params.value);
1247
+ this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
1248
+ await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
1249
+ if (data.params.value)
1250
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
1251
+ else
1252
+ AnsiLogger.setGlobalLogfile(undefined);
1253
+ }
1254
+ break;
1255
+ case 'setmjloglevel':
1256
+ if (isValidString(data.params.value, 4)) {
1257
+ this.log.debug('Matter logger level:', data.params.value);
1258
+ if (data.params.value === 'Debug') {
1259
+ Logger.level = MatterLogLevel.DEBUG;
1260
+ }
1261
+ else if (data.params.value === 'Info') {
1262
+ Logger.level = MatterLogLevel.INFO;
1263
+ }
1264
+ else if (data.params.value === 'Notice') {
1265
+ Logger.level = MatterLogLevel.NOTICE;
1266
+ }
1267
+ else if (data.params.value === 'Warn') {
1268
+ Logger.level = MatterLogLevel.WARN;
1269
+ }
1270
+ else if (data.params.value === 'Error') {
1271
+ Logger.level = MatterLogLevel.ERROR;
1272
+ }
1273
+ else if (data.params.value === 'Fatal') {
1274
+ Logger.level = MatterLogLevel.FATAL;
1275
+ }
1276
+ this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
1277
+ await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
1278
+ }
1279
+ break;
1280
+ case 'setmjlogfile':
1281
+ if (isValidBoolean(data.params.value)) {
1282
+ this.log.debug('Matter file log:', data.params.value);
1283
+ this.matterbridge.matterbridgeInformation.matterFileLogger = data.params.value;
1284
+ await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
1285
+ if (data.params.value) {
1286
+ try {
1287
+ Logger.addLogger('matterfilelogger', await this.matterbridge.createMatterFileLogger(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), true), {
1288
+ defaultLogLevel: this.matterbridge.matterbridgeInformation.matterLoggerLevel,
1289
+ logFormat: MatterLogFormat.PLAIN,
1290
+ });
1291
+ }
1292
+ catch (error) {
1293
+ this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1294
+ }
1295
+ }
1296
+ else {
1297
+ try {
1298
+ Logger.removeLogger('matterfilelogger');
1299
+ }
1300
+ catch (error) {
1301
+ this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1302
+ }
1303
+ }
1304
+ }
1305
+ break;
1306
+ case 'setmdnsinterface':
1307
+ if (isValidString(data.params.value)) {
1308
+ this.log.debug(`Matter.js mdns interface: ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
1309
+ this.matterbridge.mdnsInterface = data.params.value === '' ? undefined : data.params.value;
1310
+ this.matterbridge.matterbridgeInformation.mattermdnsinterface = this.matterbridge.mdnsInterface;
1311
+ await this.matterbridge.nodeContext?.set('mattermdnsinterface', data.params.value);
1312
+ this.wssSendRestartRequired();
1313
+ }
1314
+ break;
1315
+ case 'setipv4address':
1316
+ if (isValidString(data.params.value)) {
1317
+ this.log.debug(`Matter.js ipv4 address: ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
1318
+ this.matterbridge.ipv4address = data.params.value === '' ? undefined : data.params.value;
1319
+ this.matterbridge.matterbridgeInformation.matteripv4address = this.matterbridge.ipv4address;
1320
+ await this.matterbridge.nodeContext?.set('matteripv4address', data.params.value);
1321
+ this.wssSendRestartRequired();
1322
+ }
1323
+ break;
1324
+ case 'setipv6address':
1325
+ if (isValidString(data.params.value)) {
1326
+ this.log.debug(`Matter.js ipv6 address: ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
1327
+ this.matterbridge.ipv6address = data.params.value === '' ? undefined : data.params.value;
1328
+ this.matterbridge.matterbridgeInformation.matteripv6address = this.matterbridge.ipv6address;
1329
+ await this.matterbridge.nodeContext?.set('matteripv6address', data.params.value);
1330
+ this.wssSendRestartRequired();
1331
+ }
1332
+ break;
1333
+ case 'setmatterport':
1334
+ data.params.value = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1335
+ if (isValidNumber(data.params.value, 5540, 5580)) {
1336
+ this.log.debug(`Set matter commissioning port to ${CYAN}${data.params.value}${db}`);
1337
+ this.matterbridge.matterbridgeInformation.matterPort = data.params.value;
1338
+ await this.matterbridge.nodeContext?.set('matterport', data.params.value);
1339
+ this.wssSendRestartRequired();
1340
+ }
1341
+ else {
1342
+ this.log.debug(`Reset matter commissioning port to ${CYAN}5040${db}`);
1343
+ this.matterbridge.matterbridgeInformation.matterPort = 5040;
1344
+ await this.matterbridge.nodeContext?.set('matterport', 5040);
1345
+ this.wssSendRestartRequired();
1346
+ }
1347
+ break;
1348
+ case 'setmatterdiscriminator':
1349
+ data.params.value = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1350
+ if (isValidNumber(data.params.value, 1000, 4095)) {
1351
+ this.log.debug(`Set matter commissioning discriminator to ${CYAN}${data.params.value}${db}`);
1352
+ this.matterbridge.matterbridgeInformation.matterDiscriminator = data.params.value;
1353
+ await this.matterbridge.nodeContext?.set('matterdiscriminator', data.params.value);
1354
+ this.wssSendRestartRequired();
1355
+ }
1356
+ else {
1357
+ this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
1358
+ this.matterbridge.matterbridgeInformation.matterDiscriminator = undefined;
1359
+ await this.matterbridge.nodeContext?.remove('matterdiscriminator');
1360
+ this.wssSendRestartRequired();
1361
+ }
1362
+ break;
1363
+ case 'setmatterpasscode':
1364
+ data.params.value = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
1365
+ if (isValidNumber(data.params.value, 10000000, 90000000)) {
1366
+ this.matterbridge.matterbridgeInformation.matterPasscode = data.params.value;
1367
+ this.log.debug(`Set matter commissioning passcode to ${CYAN}${data.params.value}${db}`);
1368
+ await this.matterbridge.nodeContext?.set('matterpasscode', data.params.value);
1369
+ this.wssSendRestartRequired();
1370
+ }
1371
+ else {
1372
+ this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
1373
+ this.matterbridge.matterbridgeInformation.matterPasscode = undefined;
1374
+ await this.matterbridge.nodeContext?.remove('matterpasscode');
1375
+ this.wssSendRestartRequired();
1376
+ }
1377
+ break;
1378
+ }
1379
+ }
1477
1380
  else if (data.method === '/api/command') {
1478
1381
  if (!isValidString(data.params.command, 5)) {
1479
1382
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter command in /api/command' }));
@@ -1561,12 +1464,10 @@ export class Frontend {
1561
1464
  else {
1562
1465
  this.log.error(`Invalid method from websocket client: ${debugStringify(data)}`);
1563
1466
  client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid method' }));
1564
- return;
1565
1467
  }
1566
1468
  }
1567
1469
  catch (error) {
1568
1470
  this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
1569
- return;
1570
1471
  }
1571
1472
  }
1572
1473
  wssSendMessage(level, time, name, message) {
@@ -1652,7 +1553,15 @@ export class Frontend {
1652
1553
  this.log.debug('Sending a snackbar message to all connected clients');
1653
1554
  this.webSocketServer?.clients.forEach((client) => {
1654
1555
  if (client.readyState === WebSocket.OPEN) {
1655
- client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout, severity } }));
1556
+ client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
1557
+ }
1558
+ });
1559
+ }
1560
+ wssSendCloseSnackbarMessage(message) {
1561
+ this.log.debug('Sending a close snackbar message to all connected clients');
1562
+ this.webSocketServer?.clients.forEach((client) => {
1563
+ if (client.readyState === WebSocket.OPEN) {
1564
+ client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
1656
1565
  }
1657
1566
  });
1658
1567
  }