matterbridge 3.1.4-dev-20250717-d36e252 → 3.1.5-dev-20250718-054cd80

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/CHANGELOG.md CHANGED
@@ -8,6 +8,26 @@ If you like this project and find it useful, please consider giving it a star on
8
8
  <img src="bmc-button.svg" alt="Buy me a coffee" width="120">
9
9
  </a>
10
10
 
11
+ ## [3.1.5] - 2025-07-??
12
+
13
+ ### Added
14
+
15
+ - [error]: Added error logging functions and corresponding tests.
16
+ - [frontend]: Improved test units on Matterbridge class (total coverage 99%).
17
+
18
+ ### Changed
19
+
20
+ - [matterbridge]: Refactored initialization of DeviceManager and PluginManager
21
+ - [pluginManager]: Refactored PluginManager removing unused install/uninstall methods
22
+ - [pluginManager]: Added loading of default plugin config when a plugin is added the first time. It must be a file in the package root named '[PLUGIN-NAME].config.json'.
23
+ - [readme-dev]: Added [documentation](README-DEV.md) for default plugin config and schema files.
24
+
25
+ ### Fixed
26
+
27
+ <a href="https://www.buymeacoffee.com/luligugithub">
28
+ <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
29
+ </a>
30
+
11
31
  ## [3.1.4] - 2025-07-16
12
32
 
13
33
  ### Added
package/README-DEV.md CHANGED
@@ -298,6 +298,65 @@ The mode=`server` property of MatterbridgeEndpointOptions, allows to create an i
298
298
 
299
299
  The mode=`matter` property of MatterbridgeEndpointOptions, allows to create a (not bridged) Matter device that is added to the Matterbridge server node alongside the aggregator.
300
300
 
301
+ ## Plugin config file
302
+
303
+ Each plugin has a minimal default config file injected by Matterbridge when it is loaded:
304
+
305
+ ```typescript
306
+ {
307
+ name: plugin.name, // i.e. matterbridge-test
308
+ type: plugin.type, // i.e. AccessoryPlatform or DynamicPlatform (on the first run is AnyPlatform cause it is still unknown)
309
+ debug: false,
310
+ unregisterOnShutdown: false
311
+ }
312
+ ```
313
+
314
+ It is possible to add a different default config file to be loaded the first time the user installs the plugin.
315
+
316
+ Matterbridge (only on the first load of the plugin and if a config file is not already present in the .matterbridge directory) looks for the default config file in the root of the plugin package. The file must be named '[PLUGIN-NAME].config.json' (i.e. 'matterbridge-test.config.json').
317
+
318
+ In all subsequent loads the config file is loaded from the '.matterbridge' directory.
319
+
320
+ ## Plugin schema file
321
+
322
+ Each plugin has a minimal default schema file injected by Matterbridge when it is loaded:
323
+
324
+ ```typescript
325
+ {
326
+ title: plugin.description,
327
+ description: plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author,
328
+ type: 'object',
329
+ properties: {
330
+ name: {
331
+ description: 'Plugin name',
332
+ type: 'string',
333
+ readOnly: true,
334
+ },
335
+ type: {
336
+ description: 'Plugin type',
337
+ type: 'string',
338
+ readOnly: true,
339
+ },
340
+ debug: {
341
+ description: 'Enable the debug for the plugin (development only)',
342
+ type: 'boolean',
343
+ default: false,
344
+ },
345
+ unregisterOnShutdown: {
346
+ description: 'Unregister all devices on shutdown (development only)',
347
+ type: 'boolean',
348
+ default: false,
349
+ },
350
+ },
351
+ }
352
+ ```
353
+
354
+ It is possible to add a different default schema file.
355
+
356
+ The schema file is loaded from the root of the plugin package. The file must be named '[PLUGIN-NAME].schema.json' (i.e. 'matterbridge-test.schema.json').
357
+
358
+ The properties of the schema file shall correspond to the properties of the config file.
359
+
301
360
  # Contribution Guidelines
302
361
 
303
362
  Thank you for your interest in contributing to my project!
@@ -10,7 +10,7 @@ import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matt
10
10
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
11
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
12
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
13
- import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
13
+ import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory, wait } from './utils/export.js';
14
14
  import { dev, plg, typ } from './matterbridgeTypes.js';
15
15
  import { PluginManager } from './pluginManager.js';
16
16
  import { DeviceManager } from './deviceManager.js';
@@ -97,8 +97,8 @@ export class Matterbridge extends EventEmitter {
97
97
  log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
98
98
  matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
99
99
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
100
- plugins;
101
- devices;
100
+ plugins = new PluginManager(this);
101
+ devices = new DeviceManager(this);
102
102
  frontend = new Frontend(this);
103
103
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
104
104
  nodeStorage;
@@ -492,10 +492,8 @@ export class Matterbridge extends EventEmitter {
492
492
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
493
493
  }
494
494
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
495
- this.plugins = new PluginManager(this);
496
- await this.plugins.loadFromStorage();
497
495
  this.plugins.logLevel = this.log.logLevel;
498
- this.devices = new DeviceManager(this);
496
+ await this.plugins.loadFromStorage();
499
497
  this.devices.logLevel = this.log.logLevel;
500
498
  for (const plugin of this.plugins) {
501
499
  const packageJson = await this.plugins.parse(plugin);
@@ -565,7 +563,8 @@ export class Matterbridge extends EventEmitter {
565
563
  - nosudo: force not to use sudo to install or update packages if the internal logic fails
566
564
  - norestore: force not to automatically restore the matterbridge node storage and the matter storage from backup if it is corrupted
567
565
  - novirtual: disable the creation of the virtual devices Restart, Update and Reboot Matterbridge
568
- - ssl: enable SSL for the frontend and WebSockerServer (certificates in .matterbridge/certs directory cert.pem, key.pem and ca.pem (optional))
566
+ - ssl: enable SSL for the frontend and the WebSocketServer (the server will use the certificates and switch to https)
567
+ - mtls: enable mTLS for the frontend and the WebSocketServer (both server and client will use and require the certificates and switch to https)
569
568
  - vendorId: override the default vendorId 0xfff1
570
569
  - vendorName: override the default vendorName "Matterbridge"
571
570
  - productId: override the default productId 0x8000
@@ -984,15 +983,15 @@ export class Matterbridge extends EventEmitter {
984
983
  this.frontend.wssSendRestartRequired();
985
984
  await this.cleanup('updating...', false);
986
985
  }
987
- async unregisterAndShutdownProcess() {
986
+ async unregisterAndShutdownProcess(timeout = 1000) {
988
987
  this.log.info('Unregistering all devices and shutting down...');
989
988
  for (const plugin of this.plugins) {
990
989
  await this.removeAllBridgedEndpoints(plugin.name, 250);
991
990
  }
992
991
  this.log.debug('Waiting for the MessageExchange to finish...');
993
- await new Promise((resolve) => setTimeout(resolve, 1000));
992
+ await new Promise((resolve) => setTimeout(resolve, timeout));
994
993
  this.log.debug('Cleaning up and shutting down...');
995
- await this.cleanup('unregistered all devices and shutting down...', false);
994
+ await this.cleanup('unregistered all devices and shutting down...', false, timeout);
996
995
  }
997
996
  async shutdownProcessAndReset() {
998
997
  await this.cleanup('shutting down with reset...', false);
@@ -1166,7 +1165,7 @@ export class Matterbridge extends EventEmitter {
1166
1165
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1167
1166
  }
1168
1167
  }
1169
- async createAccessoryPlugin(plugin, device, start = false) {
1168
+ async createAccessoryPlugin(plugin, device) {
1170
1169
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1171
1170
  plugin.locked = true;
1172
1171
  plugin.device = device;
@@ -1175,8 +1174,6 @@ export class Matterbridge extends EventEmitter {
1175
1174
  plugin.serialNumber = await plugin.storageContext.get('serialNumber', '');
1176
1175
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
1177
1176
  await plugin.serverNode.add(device);
1178
- if (start)
1179
- await this.startServerNode(plugin.serverNode);
1180
1177
  }
1181
1178
  }
1182
1179
  async createDynamicPlugin(plugin) {
@@ -1236,7 +1233,7 @@ export class Matterbridge extends EventEmitter {
1236
1233
  }
1237
1234
  }
1238
1235
  this.configureTimeout = setTimeout(async () => {
1239
- for (const plugin of this.plugins) {
1236
+ for (const plugin of this.plugins.array()) {
1240
1237
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1241
1238
  continue;
1242
1239
  try {
@@ -1262,7 +1259,7 @@ export class Matterbridge extends EventEmitter {
1262
1259
  this.log.notice('Matterbridge bridge started successfully');
1263
1260
  }, 1000);
1264
1261
  }
1265
- async startChildbridge() {
1262
+ async startChildbridge(delay = 1000) {
1266
1263
  if (!this.matterStorageManager)
1267
1264
  throw new Error('No storage manager initialized');
1268
1265
  await this.startPlugins();
@@ -1270,7 +1267,7 @@ export class Matterbridge extends EventEmitter {
1270
1267
  let failCount = 0;
1271
1268
  this.startMatterInterval = setInterval(async () => {
1272
1269
  let allStarted = true;
1273
- for (const plugin of this.plugins) {
1270
+ for (const plugin of this.plugins.array()) {
1274
1271
  if (!plugin.enabled)
1275
1272
  continue;
1276
1273
  if (plugin.error) {
@@ -1289,7 +1286,7 @@ export class Matterbridge extends EventEmitter {
1289
1286
  this.log.debug(`Waiting (failSafeCount=${failCount}/${this.failCountLimit}) for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1290
1287
  failCount++;
1291
1288
  if (failCount > this.failCountLimit) {
1292
- this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error mode.`);
1289
+ this.log.error(`Error waiting for plugin ${plg}${plugin.name}${er} to load and start. Plugin is in error state.`);
1293
1290
  plugin.error = true;
1294
1291
  }
1295
1292
  }
@@ -1298,10 +1295,11 @@ export class Matterbridge extends EventEmitter {
1298
1295
  return;
1299
1296
  clearInterval(this.startMatterInterval);
1300
1297
  this.startMatterInterval = undefined;
1301
- await new Promise((resolve) => setTimeout(resolve, 1000));
1298
+ if (delay > 0)
1299
+ await wait(delay);
1302
1300
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1303
1301
  this.configureTimeout = setTimeout(async () => {
1304
- for (const plugin of this.plugins) {
1302
+ for (const plugin of this.plugins.array()) {
1305
1303
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1306
1304
  continue;
1307
1305
  try {
@@ -1,15 +1,14 @@
1
1
  import EventEmitter from 'node:events';
2
2
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from 'node-ansi-logger';
3
3
  import { plg, typ } from './matterbridgeTypes.js';
4
+ import { logError } from './utils/error.js';
4
5
  export class PluginManager extends EventEmitter {
5
6
  _plugins = new Map();
6
- nodeContext;
7
7
  matterbridge;
8
8
  log;
9
9
  constructor(matterbridge) {
10
10
  super();
11
11
  this.matterbridge = matterbridge;
12
- this.nodeContext = matterbridge.nodeContext;
13
12
  this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: matterbridge.log.logLevel });
14
13
  this.log.debug('Matterbridge plugin manager starting...');
15
14
  }
@@ -46,7 +45,7 @@ export class PluginManager extends EventEmitter {
46
45
  await callback(plugin);
47
46
  }
48
47
  catch (err) {
49
- this.log.error(`Error processing forEach plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
48
+ logError(this.log, `Error processing forEach plugin ${plg}${plugin.name}${er}`, err);
50
49
  }
51
50
  });
52
51
  await Promise.all(tasks);
@@ -55,12 +54,18 @@ export class PluginManager extends EventEmitter {
55
54
  this.log.logLevel = logLevel;
56
55
  }
57
56
  async loadFromStorage() {
58
- const pluginsArray = await this.nodeContext.get('plugins', []);
57
+ if (!this.matterbridge.nodeContext) {
58
+ throw new Error('loadFromStorage: node context is not available.');
59
+ }
60
+ const pluginsArray = await this.matterbridge.nodeContext.get('plugins', []);
59
61
  for (const plugin of pluginsArray)
60
62
  this._plugins.set(plugin.name, plugin);
61
63
  return pluginsArray;
62
64
  }
63
65
  async saveToStorage() {
66
+ if (!this.matterbridge.nodeContext) {
67
+ throw new Error('loadFromStorage: node context is not available.');
68
+ }
64
69
  const plugins = [];
65
70
  const pluginArrayFromMap = Array.from(this._plugins.values());
66
71
  for (const plugin of pluginArrayFromMap) {
@@ -74,7 +79,7 @@ export class PluginManager extends EventEmitter {
74
79
  enabled: plugin.enabled,
75
80
  });
76
81
  }
77
- await this.nodeContext.set('plugins', plugins);
82
+ await this.matterbridge.nodeContext.set('plugins', plugins);
78
83
  this.log.debug(`Saved ${BLUE}${plugins.length}${db} plugins to storage`);
79
84
  return plugins.length;
80
85
  }
@@ -153,7 +158,7 @@ export class PluginManager extends EventEmitter {
153
158
  return packageJsonPath;
154
159
  }
155
160
  catch (err) {
156
- this.log.error(`Failed to resolve plugin path ${plg}${pluginPath}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
161
+ logError(this.log, `Failed to resolve plugin path ${plg}${pluginPath}${er}`, err);
157
162
  return null;
158
163
  }
159
164
  }
@@ -286,7 +291,7 @@ export class PluginManager extends EventEmitter {
286
291
  return packageJson;
287
292
  }
288
293
  catch (err) {
289
- this.log.error(`Failed to parse package.json of plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
294
+ logError(this.log, `Failed to parse package.json of plugin ${plg}${plugin.name}${er}`, err);
290
295
  plugin.error = true;
291
296
  return null;
292
297
  }
@@ -322,7 +327,7 @@ export class PluginManager extends EventEmitter {
322
327
  return plugin;
323
328
  }
324
329
  catch (err) {
325
- this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
330
+ logError(this.log, `Failed to parse package.json of plugin ${plg}${nameOrPath}${er}`, err);
326
331
  return null;
327
332
  }
328
333
  }
@@ -357,7 +362,7 @@ export class PluginManager extends EventEmitter {
357
362
  return plugin;
358
363
  }
359
364
  catch (err) {
360
- this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
365
+ logError(this.log, `Failed to parse package.json of plugin ${plg}${nameOrPath}${er}`, err);
361
366
  return null;
362
367
  }
363
368
  }
@@ -392,7 +397,7 @@ export class PluginManager extends EventEmitter {
392
397
  return plugin;
393
398
  }
394
399
  catch (err) {
395
- this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
400
+ logError(this.log, `Failed to parse package.json of plugin ${plg}${nameOrPath}${er}`, err);
396
401
  return null;
397
402
  }
398
403
  }
@@ -427,64 +432,10 @@ export class PluginManager extends EventEmitter {
427
432
  return plugin || null;
428
433
  }
429
434
  catch (err) {
430
- this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
435
+ logError(this.log, `Failed to parse package.json of plugin ${plg}${nameOrPath}${er}`, err);
431
436
  return null;
432
437
  }
433
438
  }
434
- async install(name) {
435
- const { exec } = await import('node:child_process');
436
- await this.uninstall(name);
437
- this.log.info(`Installing plugin ${plg}${name}${nf}`);
438
- return new Promise((resolve) => {
439
- exec(`npm install -g ${name} --omit=dev`, (error, stdout, stderr) => {
440
- if (error) {
441
- this.log.error(`Failed to install plugin ${plg}${name}${er}: ${error}`);
442
- this.log.debug(`Failed to install plugin ${plg}${name}${db}: ${stderr}`);
443
- resolve(undefined);
444
- }
445
- else {
446
- this.log.info(`Installed plugin ${plg}${name}${nf}`);
447
- this.log.debug(`Installed plugin ${plg}${name}${db}: ${stdout}`);
448
- exec(`npm list -g ${name} --depth=0`, (listError, listStdout, listStderr) => {
449
- if (listError) {
450
- this.log.error(`List error: ${listError}`);
451
- resolve(undefined);
452
- }
453
- const lines = listStdout.split('\n');
454
- const versionLine = lines.find((line) => line.includes(`${name}@`));
455
- if (versionLine) {
456
- const version = versionLine.split('@')[1].trim();
457
- this.log.info(`Installed plugin ${plg}${name}@${version}${nf}`);
458
- this.emit('installed', name, version);
459
- resolve(version);
460
- }
461
- else {
462
- resolve(undefined);
463
- }
464
- });
465
- }
466
- });
467
- });
468
- }
469
- async uninstall(name) {
470
- const { exec } = await import('node:child_process');
471
- this.log.info(`Uninstalling plugin ${plg}${name}${nf}`);
472
- return new Promise((resolve) => {
473
- exec(`npm uninstall -g ${name}`, (error, stdout, stderr) => {
474
- if (error) {
475
- this.log.error(`Failed to uninstall plugin ${plg}${name}${er}: ${error}`);
476
- this.log.debug(`Failed to uninstall plugin ${plg}${name}${db}: ${stderr}`);
477
- resolve(undefined);
478
- }
479
- else {
480
- this.log.info(`Uninstalled plugin ${plg}${name}${nf}`);
481
- this.log.debug(`Uninstalled plugin ${plg}${name}${db}: ${stdout}`);
482
- this.emit('uninstalled', name);
483
- resolve(name);
484
- }
485
- });
486
- });
487
- }
488
439
  async load(plugin, start = false, message = '', configure = false) {
489
440
  const { promises } = await import('node:fs');
490
441
  const { default: path } = await import('node:path');
@@ -515,7 +466,7 @@ export class PluginManager extends EventEmitter {
515
466
  plugin.schemaJson = await this.loadSchema(plugin);
516
467
  config.name = plugin.name;
517
468
  config.version = packageJson.version;
518
- const log = new AnsiLogger({ logName: plugin.description ?? 'No description', logTimestampFormat: 4, logLevel: config.debug ? "debug" : this.matterbridge.log.logLevel });
469
+ const log = new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4, logLevel: config.debug ? "debug" : this.matterbridge.log.logLevel });
519
470
  const platform = pluginInstance.default(this.matterbridge, log, config);
520
471
  config.type = platform.type;
521
472
  platform.name = packageJson.name;
@@ -549,7 +500,7 @@ export class PluginManager extends EventEmitter {
549
500
  }
550
501
  }
551
502
  catch (err) {
552
- this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
503
+ logError(this.log, `Failed to load plugin ${plg}${plugin.name}${er}`, err);
553
504
  plugin.error = true;
554
505
  }
555
506
  return undefined;
@@ -580,7 +531,7 @@ export class PluginManager extends EventEmitter {
580
531
  }
581
532
  catch (err) {
582
533
  plugin.error = true;
583
- this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
534
+ logError(this.log, `Failed to start plugin ${plg}${plugin.name}${er}`, err);
584
535
  }
585
536
  return undefined;
586
537
  }
@@ -611,7 +562,7 @@ export class PluginManager extends EventEmitter {
611
562
  }
612
563
  catch (err) {
613
564
  plugin.error = true;
614
- this.log.error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
565
+ logError(this.log, `Failed to configure plugin ${plg}${plugin.name}${er}`, err);
615
566
  }
616
567
  return undefined;
617
568
  }
@@ -654,7 +605,7 @@ export class PluginManager extends EventEmitter {
654
605
  return plugin;
655
606
  }
656
607
  catch (err) {
657
- this.log.error(`Failed to shut down plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
608
+ logError(this.log, `Failed to shut down plugin ${plg}${plugin.name}${er}`, err);
658
609
  }
659
610
  return undefined;
660
611
  }
@@ -663,6 +614,7 @@ export class PluginManager extends EventEmitter {
663
614
  const { promises } = await import('node:fs');
664
615
  const { shelly_config, somfytahoma_config, zigbee2mqtt_config } = await import('./defaultConfigSchema.js');
665
616
  const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
617
+ const defaultConfigFile = plugin.path.replace('package.json', `${plugin.name}.config.json`);
666
618
  try {
667
619
  await promises.access(configFile);
668
620
  const data = await promises.readFile(configFile, 'utf8');
@@ -679,27 +631,37 @@ export class PluginManager extends EventEmitter {
679
631
  catch (err) {
680
632
  const nodeErr = err;
681
633
  if (nodeErr.code === 'ENOENT') {
634
+ this.log.debug(`Config file ${configFile} for plugin ${plg}${plugin.name}${db} does not exist, creating new config file...`);
682
635
  let config;
683
- if (plugin.name === 'matterbridge-zigbee2mqtt')
684
- config = zigbee2mqtt_config;
685
- else if (plugin.name === 'matterbridge-somfy-tahoma')
686
- config = somfytahoma_config;
687
- else if (plugin.name === 'matterbridge-shelly')
688
- config = shelly_config;
689
- else
690
- config = { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
636
+ try {
637
+ await promises.access(defaultConfigFile);
638
+ const data = await promises.readFile(defaultConfigFile, 'utf8');
639
+ config = JSON.parse(data);
640
+ this.log.debug(`Loaded default config file ${defaultConfigFile} for plugin ${plg}${plugin.name}${db}.`);
641
+ }
642
+ catch (_err) {
643
+ this.log.debug(`Default config file ${defaultConfigFile} for plugin ${plg}${plugin.name}${db} does not exist, creating new config file...`);
644
+ if (plugin.name === 'matterbridge-zigbee2mqtt')
645
+ config = zigbee2mqtt_config;
646
+ else if (plugin.name === 'matterbridge-somfy-tahoma')
647
+ config = somfytahoma_config;
648
+ else if (plugin.name === 'matterbridge-shelly')
649
+ config = shelly_config;
650
+ else
651
+ config = { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
652
+ }
691
653
  try {
692
654
  await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
693
655
  this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
694
656
  return config;
695
657
  }
696
658
  catch (err) {
697
- this.log.error(`Error creating config file ${configFile} for plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
659
+ logError(this.log, `Error creating config file ${configFile} for plugin ${plg}${plugin.name}${er}`, err);
698
660
  return config;
699
661
  }
700
662
  }
701
663
  else {
702
- this.log.error(`Error accessing config file ${configFile} for plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
664
+ logError(this.log, `Error accessing config file ${configFile} for plugin ${plg}${plugin.name}${er}`, err);
703
665
  return { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
704
666
  }
705
667
  }
@@ -721,7 +683,7 @@ export class PluginManager extends EventEmitter {
721
683
  return Promise.resolve();
722
684
  }
723
685
  catch (err) {
724
- this.log.error(`Error saving config file ${configFile} for plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
686
+ logError(this.log, `Error saving config file ${configFile} for plugin ${plg}${plugin.name}${er}`, err);
725
687
  return Promise.reject(err);
726
688
  }
727
689
  }
@@ -745,7 +707,7 @@ export class PluginManager extends EventEmitter {
745
707
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
746
708
  }
747
709
  catch (err) {
748
- this.log.error(`Error saving config file ${configFile} for plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
710
+ logError(this.log, `Error saving config file ${configFile} for plugin ${plg}${plugin.name}${er}`, err);
749
711
  return;
750
712
  }
751
713
  }
@@ -0,0 +1,9 @@
1
+ import { inspect } from 'node:util';
2
+ export function logError(log, message, error) {
3
+ log.error(`${message}: ${error instanceof Error ? error.message + '\nStack:\n' + error.stack : error}`);
4
+ }
5
+ export function inspectError(log, message, error) {
6
+ const errorMessage = error instanceof Error ? `${error.message}\n` : '';
7
+ const inspectedError = inspect(error, { depth: 10, colors: true, showHidden: false });
8
+ log.error(`${message}: ${errorMessage}${inspectedError}`);
9
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.1.4-dev-20250717-d36e252",
3
+ "version": "3.1.5-dev-20250718-054cd80",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.1.4-dev-20250717-d36e252",
9
+ "version": "3.1.5-dev-20250718-054cd80",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.15.1",
@@ -151,9 +151,9 @@
151
151
  }
152
152
  },
153
153
  "node_modules/@noble/curves": {
154
- "version": "1.9.2",
155
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz",
156
- "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==",
154
+ "version": "1.9.3",
155
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.3.tgz",
156
+ "integrity": "sha512-NiHFh8qtZRREtY0Bpup+xpmLnB0bn9UAtj8CARBc2x1zjpVLDC84u+Bvy2+uaSgA3AmMP9zsacMZT1echgVAdQ==",
157
157
  "license": "MIT",
158
158
  "dependencies": {
159
159
  "@noble/hashes": "1.8.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.1.4-dev-20250717-d36e252",
3
+ "version": "3.1.5-dev-20250718-054cd80",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",