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 +20 -0
- package/README-DEV.md +59 -0
- package/dist/matterbridge.js +17 -19
- package/dist/pluginManager.js +44 -82
- package/dist/utils/error.js +9 -0
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
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!
|
package/dist/matterbridge.js
CHANGED
|
@@ -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.
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
package/dist/pluginManager.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
config =
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
config
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.1.
|
|
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.
|
|
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.
|
|
155
|
-
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.
|
|
156
|
-
"integrity": "sha512-
|
|
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