matterbridge 3.3.0-dev-20251004-43d8106 → 3.3.1-dev-20251007-4e5eaac
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 +29 -3
- package/README.md +9 -1
- package/dist/broadcastServer.js +73 -0
- package/dist/broadcastServerTypes.js +1 -0
- package/dist/deviceManager.js +44 -5
- package/dist/frontend.js +150 -190
- package/dist/helpers.js +4 -4
- package/dist/index.js +0 -12
- package/dist/matterbridge.js +112 -117
- package/dist/matterbridgePlatform.js +1 -1
- package/dist/pluginManager.js +130 -5
- package/dist/shelly.js +4 -4
- package/dist/update.js +0 -2
- package/dist/utils/network.js +71 -38
- package/dist/utils/spawn.js +5 -6
- package/frontend/build/assets/index.js +4 -7
- package/frontend/build/assets/vendor_mui.js +1 -1
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +44 -44
- package/package.json +2 -2
- package/dist/globalMatterbridge.js +0 -23
package/dist/matterbridge.js
CHANGED
|
@@ -9,9 +9,13 @@ import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat a
|
|
|
9
9
|
import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
10
10
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
11
11
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
12
|
-
import { getParameter, getIntParameter, hasParameter
|
|
12
|
+
import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
|
|
13
|
+
import { copyDirectory } from './utils/copyDirectory.js';
|
|
14
|
+
import { createDirectory } from './utils/createDirectory.js';
|
|
15
|
+
import { isValidString, parseVersionString, isValidNumber } from './utils/isvalid.js';
|
|
16
|
+
import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
|
|
13
17
|
import { withTimeout, waiter, wait } from './utils/wait.js';
|
|
14
|
-
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ
|
|
18
|
+
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from './matterbridgeTypes.js';
|
|
15
19
|
import { PluginManager } from './pluginManager.js';
|
|
16
20
|
import { DeviceManager } from './deviceManager.js';
|
|
17
21
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
@@ -40,38 +44,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
40
44
|
heapTotal: '',
|
|
41
45
|
heapUsed: '',
|
|
42
46
|
};
|
|
43
|
-
matterbridgeInformation = {
|
|
44
|
-
homeDirectory: '',
|
|
45
|
-
rootDirectory: '',
|
|
46
|
-
matterbridgeDirectory: '',
|
|
47
|
-
matterbridgePluginDirectory: '',
|
|
48
|
-
matterbridgeCertDirectory: '',
|
|
49
|
-
globalModulesDirectory: '',
|
|
50
|
-
matterbridgeVersion: '',
|
|
51
|
-
matterbridgeLatestVersion: '',
|
|
52
|
-
matterbridgeDevVersion: '',
|
|
53
|
-
bridgeMode: '',
|
|
54
|
-
restartMode: '',
|
|
55
|
-
virtualMode: 'outlet',
|
|
56
|
-
readOnly: hasParameter('readonly') || hasParameter('shelly'),
|
|
57
|
-
shellyBoard: hasParameter('shelly'),
|
|
58
|
-
shellySysUpdate: false,
|
|
59
|
-
shellyMainUpdate: false,
|
|
60
|
-
profile: getParameter('profile'),
|
|
61
|
-
loggerLevel: "info",
|
|
62
|
-
fileLogger: false,
|
|
63
|
-
matterLoggerLevel: MatterLogLevel.INFO,
|
|
64
|
-
matterFileLogger: false,
|
|
65
|
-
matterMdnsInterface: undefined,
|
|
66
|
-
matterIpv4Address: undefined,
|
|
67
|
-
matterIpv6Address: undefined,
|
|
68
|
-
matterPort: 5540,
|
|
69
|
-
matterDiscriminator: undefined,
|
|
70
|
-
matterPasscode: undefined,
|
|
71
|
-
restartRequired: false,
|
|
72
|
-
fixedRestartRequired: false,
|
|
73
|
-
updateRequired: false,
|
|
74
|
-
};
|
|
75
47
|
homeDirectory = '';
|
|
76
48
|
rootDirectory = '';
|
|
77
49
|
matterbridgeDirectory = '';
|
|
@@ -81,18 +53,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
81
53
|
matterbridgeVersion = '';
|
|
82
54
|
matterbridgeLatestVersion = '';
|
|
83
55
|
matterbridgeDevVersion = '';
|
|
56
|
+
frontendVersion = '';
|
|
84
57
|
bridgeMode = '';
|
|
85
58
|
restartMode = '';
|
|
59
|
+
virtualMode = 'outlet';
|
|
86
60
|
profile = getParameter('profile');
|
|
87
|
-
shutdown = false;
|
|
88
|
-
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
89
61
|
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
62
|
+
fileLogger = false;
|
|
90
63
|
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
64
|
+
matterFileLogger = false;
|
|
65
|
+
readOnly = hasParameter('readonly') || hasParameter('shelly');
|
|
66
|
+
shellyBoard = hasParameter('shelly');
|
|
67
|
+
shellySysUpdate = false;
|
|
68
|
+
shellyMainUpdate = false;
|
|
69
|
+
restartRequired = false;
|
|
70
|
+
fixedRestartRequired = false;
|
|
71
|
+
updateRequired = false;
|
|
91
72
|
plugins = new PluginManager(this);
|
|
92
|
-
devices = new DeviceManager(
|
|
73
|
+
devices = new DeviceManager();
|
|
93
74
|
frontend = new Frontend(this);
|
|
94
75
|
nodeStorage;
|
|
95
76
|
nodeContext;
|
|
77
|
+
static instance;
|
|
78
|
+
shutdown = false;
|
|
79
|
+
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
96
80
|
hasCleanupStarted = false;
|
|
97
81
|
initialized = false;
|
|
98
82
|
startMatterInterval;
|
|
@@ -111,8 +95,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
111
95
|
matterbridgeContext;
|
|
112
96
|
controllerContext;
|
|
113
97
|
mdnsInterface;
|
|
114
|
-
|
|
115
|
-
|
|
98
|
+
ipv4Address;
|
|
99
|
+
ipv6Address;
|
|
116
100
|
port;
|
|
117
101
|
passcode;
|
|
118
102
|
discriminator;
|
|
@@ -127,35 +111,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
127
111
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
128
112
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
129
113
|
advertisingNodes = new Map();
|
|
130
|
-
static instance;
|
|
131
114
|
constructor() {
|
|
132
115
|
super();
|
|
133
116
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
134
117
|
}
|
|
135
|
-
async setLogLevel(logLevel) {
|
|
136
|
-
if (this.log)
|
|
137
|
-
this.log.logLevel = logLevel;
|
|
138
|
-
this.matterbridgeInformation.loggerLevel = logLevel;
|
|
139
|
-
this.frontend.logLevel = logLevel;
|
|
140
|
-
MatterbridgeEndpoint.logLevel = logLevel;
|
|
141
|
-
if (this.devices)
|
|
142
|
-
this.devices.logLevel = logLevel;
|
|
143
|
-
if (this.plugins)
|
|
144
|
-
this.plugins.logLevel = logLevel;
|
|
145
|
-
for (const plugin of this.plugins) {
|
|
146
|
-
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
147
|
-
continue;
|
|
148
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
149
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
150
|
-
}
|
|
151
|
-
let callbackLogLevel = "notice";
|
|
152
|
-
if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
153
|
-
callbackLogLevel = "info";
|
|
154
|
-
if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
155
|
-
callbackLogLevel = "debug";
|
|
156
|
-
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
157
|
-
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
158
|
-
}
|
|
159
118
|
static async loadInstance(initialize = false) {
|
|
160
119
|
if (!Matterbridge.instance) {
|
|
161
120
|
if (hasParameter('debug'))
|
|
@@ -203,23 +162,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
203
162
|
if (hasParameter('docker'))
|
|
204
163
|
this.restartMode = 'docker';
|
|
205
164
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
206
|
-
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
207
165
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
208
166
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
209
|
-
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
210
167
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
211
168
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
212
169
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
213
170
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
214
|
-
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
215
171
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
216
172
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
217
|
-
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
218
173
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
219
174
|
const { fileURLToPath } = await import('node:url');
|
|
220
175
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
221
176
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
222
|
-
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
223
177
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
224
178
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
225
179
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
@@ -349,17 +303,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
349
303
|
}
|
|
350
304
|
}
|
|
351
305
|
else {
|
|
352
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.
|
|
306
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
353
307
|
}
|
|
354
308
|
this.frontend.logLevel = this.log.logLevel;
|
|
355
309
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
356
|
-
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
357
310
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
358
311
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
359
|
-
this.
|
|
312
|
+
this.fileLogger = true;
|
|
360
313
|
}
|
|
361
314
|
this.log.notice('Matterbridge is starting...');
|
|
362
|
-
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.
|
|
315
|
+
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
363
316
|
if (this.profile !== undefined)
|
|
364
317
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
365
318
|
if (hasParameter('matterlogger')) {
|
|
@@ -388,18 +341,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
388
341
|
}
|
|
389
342
|
}
|
|
390
343
|
else {
|
|
391
|
-
Logger.level = (await this.nodeContext.get('matterLogLevel', this.
|
|
344
|
+
Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
392
345
|
}
|
|
393
346
|
Logger.format = MatterLogFormat.ANSI;
|
|
394
347
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
395
|
-
this.
|
|
348
|
+
this.matterFileLogger = true;
|
|
396
349
|
}
|
|
397
|
-
Logger.destinations.default.write = this.createDestinationMatterLogger(this.
|
|
398
|
-
this.
|
|
399
|
-
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
350
|
+
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
351
|
+
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
|
|
400
352
|
const networkInterfaces = os.networkInterfaces();
|
|
401
353
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
402
|
-
const
|
|
354
|
+
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
403
355
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
404
356
|
if (ifaces && ifaces.length > 0) {
|
|
405
357
|
this.log.debug(`Network interface ${BLUE}${ifaceName}${db}:`);
|
|
@@ -418,8 +370,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
418
370
|
this.mdnsInterface = undefined;
|
|
419
371
|
}
|
|
420
372
|
if (this.mdnsInterface) {
|
|
421
|
-
if (!
|
|
422
|
-
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${
|
|
373
|
+
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
374
|
+
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
423
375
|
this.mdnsInterface = undefined;
|
|
424
376
|
await this.nodeContext.remove('mattermdnsinterface');
|
|
425
377
|
}
|
|
@@ -430,64 +382,64 @@ export class Matterbridge extends EventEmitter {
|
|
|
430
382
|
if (this.mdnsInterface)
|
|
431
383
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
432
384
|
if (hasParameter('ipv4address')) {
|
|
433
|
-
this.
|
|
385
|
+
this.ipv4Address = getParameter('ipv4address');
|
|
434
386
|
}
|
|
435
387
|
else {
|
|
436
|
-
this.
|
|
437
|
-
if (this.
|
|
438
|
-
this.
|
|
388
|
+
this.ipv4Address = await this.nodeContext.get('matteripv4address', undefined);
|
|
389
|
+
if (this.ipv4Address === '')
|
|
390
|
+
this.ipv4Address = undefined;
|
|
439
391
|
}
|
|
440
|
-
if (this.
|
|
392
|
+
if (this.ipv4Address) {
|
|
441
393
|
let isValid = false;
|
|
442
394
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
443
|
-
if (ifaces && ifaces.find((iface) => iface.address === this.
|
|
444
|
-
this.log.info(`Using ipv4address ${CYAN}${this.
|
|
395
|
+
if (ifaces && ifaces.find((iface) => iface.address === this.ipv4Address)) {
|
|
396
|
+
this.log.info(`Using ipv4address ${CYAN}${this.ipv4Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
445
397
|
isValid = true;
|
|
446
398
|
break;
|
|
447
399
|
}
|
|
448
400
|
}
|
|
449
401
|
if (!isValid) {
|
|
450
|
-
this.log.error(`Invalid ipv4address: ${this.
|
|
451
|
-
this.
|
|
402
|
+
this.log.error(`Invalid ipv4address: ${this.ipv4Address}. Using all available addresses.`);
|
|
403
|
+
this.ipv4Address = undefined;
|
|
452
404
|
await this.nodeContext.remove('matteripv4address');
|
|
453
405
|
}
|
|
454
406
|
}
|
|
455
407
|
if (hasParameter('ipv6address')) {
|
|
456
|
-
this.
|
|
408
|
+
this.ipv6Address = getParameter('ipv6address');
|
|
457
409
|
}
|
|
458
410
|
else {
|
|
459
|
-
this.
|
|
460
|
-
if (this.
|
|
461
|
-
this.
|
|
411
|
+
this.ipv6Address = await this.nodeContext?.get('matteripv6address', undefined);
|
|
412
|
+
if (this.ipv6Address === '')
|
|
413
|
+
this.ipv6Address = undefined;
|
|
462
414
|
}
|
|
463
|
-
if (this.
|
|
415
|
+
if (this.ipv6Address) {
|
|
464
416
|
let isValid = false;
|
|
465
417
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
466
|
-
if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.
|
|
467
|
-
this.log.info(`Using ipv6address ${CYAN}${this.
|
|
418
|
+
if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.ipv6Address)) {
|
|
419
|
+
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
468
420
|
isValid = true;
|
|
469
421
|
break;
|
|
470
422
|
}
|
|
471
|
-
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.
|
|
472
|
-
this.log.info(`Using ipv6address ${CYAN}${this.
|
|
423
|
+
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
424
|
+
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
473
425
|
isValid = true;
|
|
474
426
|
break;
|
|
475
427
|
}
|
|
476
428
|
}
|
|
477
429
|
if (!isValid) {
|
|
478
|
-
this.log.error(`Invalid ipv6address: ${this.
|
|
479
|
-
this.
|
|
430
|
+
this.log.error(`Invalid ipv6address: ${this.ipv6Address}. Using all available addresses.`);
|
|
431
|
+
this.ipv6Address = undefined;
|
|
480
432
|
await this.nodeContext.remove('matteripv6address');
|
|
481
433
|
}
|
|
482
434
|
}
|
|
483
435
|
if (hasParameter('novirtual')) {
|
|
484
|
-
this.
|
|
436
|
+
this.virtualMode = 'disabled';
|
|
485
437
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
486
438
|
}
|
|
487
439
|
else {
|
|
488
|
-
this.
|
|
440
|
+
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
489
441
|
}
|
|
490
|
-
this.log.debug(`Virtual mode ${this.
|
|
442
|
+
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
491
443
|
this.plugins.logLevel = this.log.logLevel;
|
|
492
444
|
await this.plugins.loadFromStorage();
|
|
493
445
|
this.devices.logLevel = this.log.logLevel;
|
|
@@ -497,7 +449,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
497
449
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
498
450
|
try {
|
|
499
451
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
500
|
-
await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], plugin.name);
|
|
452
|
+
await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], 'install', plugin.name);
|
|
501
453
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
502
454
|
plugin.error = false;
|
|
503
455
|
}
|
|
@@ -801,6 +753,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
753
|
this.systemInformation.interfaceName = '';
|
|
802
754
|
this.systemInformation.ipv4Address = '';
|
|
803
755
|
this.systemInformation.ipv6Address = '';
|
|
756
|
+
this.systemInformation.macAddress = '';
|
|
804
757
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
805
758
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
806
759
|
continue;
|
|
@@ -837,9 +790,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
837
790
|
this.systemInformation.osRelease = os.release();
|
|
838
791
|
this.systemInformation.osPlatform = os.platform();
|
|
839
792
|
this.systemInformation.osArch = os.arch();
|
|
840
|
-
this.systemInformation.totalMemory = (os.totalmem()
|
|
841
|
-
this.systemInformation.freeMemory = (os.freemem()
|
|
842
|
-
this.systemInformation.systemUptime = (os.uptime()
|
|
793
|
+
this.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
794
|
+
this.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
795
|
+
this.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
796
|
+
this.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
|
|
797
|
+
this.systemInformation.cpuUsage = '0.00 %';
|
|
798
|
+
this.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
799
|
+
this.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
800
|
+
this.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
843
801
|
this.log.debug('Host System Information:');
|
|
844
802
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
845
803
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -855,18 +813,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
855
813
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
856
814
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
857
815
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
816
|
+
this.log.debug(`- Process Uptime: ${this.systemInformation.processUptime}`);
|
|
817
|
+
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
818
|
+
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
819
|
+
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
858
820
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
859
821
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
860
822
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
861
823
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
862
824
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
863
825
|
if (this.nodeContext)
|
|
864
|
-
this.globalModulesDirectory =
|
|
826
|
+
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
865
827
|
if (this.globalModulesDirectory === '') {
|
|
866
828
|
this.log.debug(`Getting global node_modules directory...`);
|
|
867
829
|
try {
|
|
868
830
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
869
|
-
this.
|
|
831
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
870
832
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
871
833
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
872
834
|
}
|
|
@@ -878,7 +840,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
878
840
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
879
841
|
try {
|
|
880
842
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
881
|
-
this.
|
|
843
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
882
844
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
883
845
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
884
846
|
}
|
|
@@ -886,21 +848,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
886
848
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
887
849
|
}
|
|
888
850
|
}
|
|
851
|
+
this.log.debug(`Reading matterbridge package.json...`);
|
|
889
852
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
890
853
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
891
|
-
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
892
854
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
893
855
|
if (this.nodeContext)
|
|
894
|
-
this.matterbridgeLatestVersion =
|
|
856
|
+
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
895
857
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
896
858
|
if (this.nodeContext)
|
|
897
|
-
this.matterbridgeDevVersion =
|
|
859
|
+
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
898
860
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
861
|
+
this.log.debug(`Reading frontend package.json...`);
|
|
862
|
+
const frontendPackageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
863
|
+
this.frontendVersion = frontendPackageJson.version;
|
|
864
|
+
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
899
865
|
const currentDir = process.cwd();
|
|
900
866
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
901
867
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
902
868
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
903
869
|
}
|
|
870
|
+
async setLogLevel(logLevel) {
|
|
871
|
+
if (this.log)
|
|
872
|
+
this.log.logLevel = logLevel;
|
|
873
|
+
this.frontend.logLevel = logLevel;
|
|
874
|
+
MatterbridgeEndpoint.logLevel = logLevel;
|
|
875
|
+
if (this.devices)
|
|
876
|
+
this.devices.logLevel = logLevel;
|
|
877
|
+
if (this.plugins)
|
|
878
|
+
this.plugins.logLevel = logLevel;
|
|
879
|
+
for (const plugin of this.plugins) {
|
|
880
|
+
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
881
|
+
continue;
|
|
882
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
883
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
884
|
+
}
|
|
885
|
+
let callbackLogLevel = "notice";
|
|
886
|
+
if (this.log.logLevel === "info" || Logger.level === MatterLogLevel.INFO)
|
|
887
|
+
callbackLogLevel = "info";
|
|
888
|
+
if (this.log.logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
889
|
+
callbackLogLevel = "debug";
|
|
890
|
+
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
891
|
+
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
892
|
+
}
|
|
893
|
+
getLogLevel() {
|
|
894
|
+
return this.log.logLevel;
|
|
895
|
+
}
|
|
904
896
|
createDestinationMatterLogger(fileLogger) {
|
|
905
897
|
this.matterLog.logNameColor = '\x1b[34m';
|
|
906
898
|
if (fileLogger) {
|
|
@@ -942,7 +934,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
942
934
|
this.log.info('Updating matterbridge...');
|
|
943
935
|
try {
|
|
944
936
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
945
|
-
await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'matterbridge');
|
|
937
|
+
await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'install', 'matterbridge');
|
|
946
938
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
947
939
|
}
|
|
948
940
|
catch (error) {
|
|
@@ -1066,6 +1058,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1066
1058
|
}
|
|
1067
1059
|
await this.stopMatterStorage();
|
|
1068
1060
|
await this.frontend.stop();
|
|
1061
|
+
this.frontend.destroy();
|
|
1062
|
+
this.plugins.destroy();
|
|
1063
|
+
this.devices.destroy();
|
|
1069
1064
|
if (this.nodeStorage && this.nodeContext) {
|
|
1070
1065
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1071
1066
|
await this.nodeContext.close();
|
|
@@ -1400,8 +1395,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1400
1395
|
const serverNode = await ServerNode.create({
|
|
1401
1396
|
id: storeId,
|
|
1402
1397
|
network: {
|
|
1403
|
-
listeningAddressIpv4: this.
|
|
1404
|
-
listeningAddressIpv6: this.
|
|
1398
|
+
listeningAddressIpv4: this.ipv4Address,
|
|
1399
|
+
listeningAddressIpv6: this.ipv6Address,
|
|
1405
1400
|
port,
|
|
1406
1401
|
},
|
|
1407
1402
|
operationalCredentials: {
|
package/dist/pluginManager.js
CHANGED
|
@@ -1,16 +1,59 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
-
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from 'node-ansi-logger';
|
|
2
|
+
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr, debugStringify, CYAN } from 'node-ansi-logger';
|
|
3
3
|
import { plg, typ } from './matterbridgeTypes.js';
|
|
4
|
-
import { logError } from './utils/error.js';
|
|
4
|
+
import { inspectError, logError } from './utils/error.js';
|
|
5
|
+
import { BroadcastServer } from './broadcastServer.js';
|
|
5
6
|
export class PluginManager extends EventEmitter {
|
|
6
7
|
_plugins = new Map();
|
|
7
8
|
matterbridge;
|
|
8
9
|
log;
|
|
10
|
+
server;
|
|
9
11
|
constructor(matterbridge) {
|
|
10
12
|
super();
|
|
11
13
|
this.matterbridge = matterbridge;
|
|
12
14
|
this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: matterbridge.log.logLevel });
|
|
13
15
|
this.log.debug('Matterbridge plugin manager starting...');
|
|
16
|
+
this.server = new BroadcastServer('plugins', this.log);
|
|
17
|
+
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
18
|
+
this.log.debug('Matterbridge plugin manager started');
|
|
19
|
+
}
|
|
20
|
+
destroy() {
|
|
21
|
+
this.server.close();
|
|
22
|
+
}
|
|
23
|
+
async msgHandler(msg) {
|
|
24
|
+
if (!this.server.isWorkerRequest(msg, msg.type))
|
|
25
|
+
return;
|
|
26
|
+
if (!msg.id || (msg.dst !== 'all' && msg.dst !== 'plugins'))
|
|
27
|
+
return;
|
|
28
|
+
this.log.debug(`**Received request message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
29
|
+
switch (msg.type) {
|
|
30
|
+
case 'plugins_length':
|
|
31
|
+
this.server.respond({ ...msg, id: msg.id, response: { length: this.length } });
|
|
32
|
+
break;
|
|
33
|
+
case 'plugins_size':
|
|
34
|
+
this.server.respond({ ...msg, id: msg.id, response: { size: this.size } });
|
|
35
|
+
break;
|
|
36
|
+
case 'plugins_has':
|
|
37
|
+
this.server.respond({ ...msg, id: msg.id, response: { has: this.has(msg.params.name) } });
|
|
38
|
+
break;
|
|
39
|
+
case 'plugins_get':
|
|
40
|
+
this.server.respond({ ...msg, id: msg.id, response: { plugin: this.get(msg.params.name) } });
|
|
41
|
+
break;
|
|
42
|
+
case 'plugins_set':
|
|
43
|
+
this.server.respond({ ...msg, id: msg.id, response: { plugin: this.set(msg.params.plugin) } });
|
|
44
|
+
break;
|
|
45
|
+
case 'plugins_baseArray':
|
|
46
|
+
this.server.respond({ ...msg, id: msg.id, response: { plugins: this.baseArray() } });
|
|
47
|
+
break;
|
|
48
|
+
case 'plugins_install':
|
|
49
|
+
this.server.respond({ ...msg, id: msg.id, response: { packageName: msg.params.packageName, success: await this.install(msg.params.packageName) } });
|
|
50
|
+
break;
|
|
51
|
+
case 'plugins_uninstall':
|
|
52
|
+
this.server.respond({ ...msg, id: msg.id, response: { packageName: msg.params.packageName, success: await this.uninstall(msg.params.packageName) } });
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
this.log.warn(`Unknown broadcast message ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
|
|
56
|
+
}
|
|
14
57
|
}
|
|
15
58
|
get length() {
|
|
16
59
|
return this._plugins.size;
|
|
@@ -34,6 +77,39 @@ export class PluginManager extends EventEmitter {
|
|
|
34
77
|
array() {
|
|
35
78
|
return Array.from(this._plugins.values());
|
|
36
79
|
}
|
|
80
|
+
baseArray() {
|
|
81
|
+
const basePlugins = [];
|
|
82
|
+
for (const plugin of this._plugins.values()) {
|
|
83
|
+
basePlugins.push({
|
|
84
|
+
name: plugin.name,
|
|
85
|
+
version: plugin.version,
|
|
86
|
+
description: plugin.description,
|
|
87
|
+
author: plugin.author,
|
|
88
|
+
path: plugin.path,
|
|
89
|
+
type: plugin.type,
|
|
90
|
+
latestVersion: plugin.latestVersion,
|
|
91
|
+
devVersion: plugin.devVersion,
|
|
92
|
+
homepage: plugin.homepage,
|
|
93
|
+
help: plugin.help,
|
|
94
|
+
changelog: plugin.changelog,
|
|
95
|
+
funding: plugin.funding,
|
|
96
|
+
locked: plugin.locked,
|
|
97
|
+
error: plugin.error,
|
|
98
|
+
enabled: plugin.enabled,
|
|
99
|
+
loaded: plugin.loaded,
|
|
100
|
+
started: plugin.started,
|
|
101
|
+
configured: plugin.configured,
|
|
102
|
+
restartRequired: plugin.restartRequired,
|
|
103
|
+
registeredDevices: plugin.registeredDevices,
|
|
104
|
+
configJson: plugin.configJson,
|
|
105
|
+
schemaJson: plugin.schemaJson,
|
|
106
|
+
hasWhiteList: plugin.hasWhiteList,
|
|
107
|
+
hasBlackList: plugin.hasBlackList,
|
|
108
|
+
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return basePlugins;
|
|
112
|
+
}
|
|
37
113
|
[Symbol.iterator]() {
|
|
38
114
|
return this._plugins.values();
|
|
39
115
|
}
|
|
@@ -67,8 +143,7 @@ export class PluginManager extends EventEmitter {
|
|
|
67
143
|
throw new Error('loadFromStorage: node context is not available.');
|
|
68
144
|
}
|
|
69
145
|
const plugins = [];
|
|
70
|
-
const
|
|
71
|
-
for (const plugin of pluginArrayFromMap) {
|
|
146
|
+
for (const plugin of this.array()) {
|
|
72
147
|
plugins.push({
|
|
73
148
|
name: plugin.name,
|
|
74
149
|
path: plugin.path,
|
|
@@ -162,6 +237,52 @@ export class PluginManager extends EventEmitter {
|
|
|
162
237
|
return null;
|
|
163
238
|
}
|
|
164
239
|
}
|
|
240
|
+
async install(packageName) {
|
|
241
|
+
const { spawnCommand } = await import('./utils/spawn.js');
|
|
242
|
+
try {
|
|
243
|
+
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', packageName, '--omit=dev', '--verbose'], 'install', packageName);
|
|
244
|
+
this.matterbridge.restartRequired = true;
|
|
245
|
+
this.matterbridge.fixedRestartRequired = true;
|
|
246
|
+
packageName = packageName.replace(/@.*$/, '');
|
|
247
|
+
if (packageName !== 'matterbridge') {
|
|
248
|
+
if (!this.has(packageName))
|
|
249
|
+
await this.add(packageName);
|
|
250
|
+
const plugin = this.get(packageName);
|
|
251
|
+
if (plugin && !plugin.loaded)
|
|
252
|
+
await this.load(plugin);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
if (this.matterbridge.restartMode !== '') {
|
|
256
|
+
await this.matterbridge.shutdownProcess();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
inspectError(this.log, `Failed to install package ${plg}${packageName}${er}`, error);
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async uninstall(packageName) {
|
|
267
|
+
const { spawnCommand } = await import('./utils/spawn.js');
|
|
268
|
+
packageName = packageName.replace(/@.*$/, '');
|
|
269
|
+
if (packageName === 'matterbridge')
|
|
270
|
+
return false;
|
|
271
|
+
try {
|
|
272
|
+
if (this.has(packageName)) {
|
|
273
|
+
const plugin = this.get(packageName);
|
|
274
|
+
if (plugin && plugin.loaded)
|
|
275
|
+
await this.shutdown(plugin, 'Matterbridge is uninstalling the plugin');
|
|
276
|
+
await this.remove(packageName);
|
|
277
|
+
}
|
|
278
|
+
await spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', packageName, '--verbose'], 'uninstall', packageName);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
inspectError(this.log, `Failed to uninstall package ${plg}${packageName}${er}`, error);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
165
286
|
getAuthor(packageJson) {
|
|
166
287
|
if (packageJson.author && typeof packageJson.author === 'string')
|
|
167
288
|
return packageJson.author;
|
|
@@ -424,12 +545,16 @@ export class PluginManager extends EventEmitter {
|
|
|
424
545
|
version: packageJson.version,
|
|
425
546
|
description: packageJson.description,
|
|
426
547
|
author: this.getAuthor(packageJson),
|
|
548
|
+
homepage: this.getHomepage(packageJson),
|
|
549
|
+
help: this.getHelp(packageJson),
|
|
550
|
+
changelog: this.getChangelog(packageJson),
|
|
551
|
+
funding: this.getFunding(packageJson),
|
|
427
552
|
});
|
|
428
553
|
this.log.info(`Added plugin ${plg}${packageJson.name}${nf}`);
|
|
429
554
|
await this.saveToStorage();
|
|
430
555
|
const plugin = this._plugins.get(packageJson.name);
|
|
431
556
|
this.emit('added', packageJson.name);
|
|
432
|
-
return plugin
|
|
557
|
+
return plugin;
|
|
433
558
|
}
|
|
434
559
|
catch (err) {
|
|
435
560
|
logError(this.log, `Failed to parse package.json of plugin ${plg}${nameOrPath}${er}`, err);
|