matterbridge 2.1.6-dev.3 → 2.1.6-dev.5
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 +6 -2
- package/dist/cli.js +129 -85
- package/dist/frontend.js +38 -98
- package/dist/index.js +6 -8
- package/dist/matterbridge.js +56 -63
- package/dist/matterbridgeEndpoint.js +1 -1
- package/dist/matterbridgeEndpointHelpers.js +2 -2
- package/dist/matterbridgePlatform.js +2 -2
- package/dist/pluginManager.js +42 -27
- package/dist/utils/colorUtils.js +1 -1
- package/dist/utils/export.js +2 -0
- package/dist/utils/isvalid.js +50 -0
- package/dist/utils/parameter.js +26 -0
- package/dist/utils/utils.js +19 -81
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.a241d4f0.js → main.be75f5a3.js} +13 -13
- package/frontend/build/static/js/main.be75f5a3.js.map +1 -0
- package/npm-shrinkwrap.json +44 -44
- package/package.json +2 -2
- package/frontend/build/static/js/main.a241d4f0.js.map +0 -1
- /package/frontend/build/static/js/{main.a241d4f0.js.LICENSE.txt → main.be75f5a3.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import EventEmitter from 'events';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { randomBytes } from 'crypto';
|
|
8
|
-
import { NodeStorageManager } from './storage/export.js';
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import EventEmitter from 'node:events';
|
|
9
5
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from './logger/export.js';
|
|
10
|
-
import {
|
|
6
|
+
import { NodeStorageManager } from './storage/export.js';
|
|
7
|
+
import { getParameter, getIntParameter, hasParameter } from './utils/export.js';
|
|
8
|
+
import { logInterfaces, copyDirectory, getNpmPackageVersion, getGlobalNodeModules } from './utils/utils.js';
|
|
11
9
|
import { PluginManager } from './pluginManager.js';
|
|
12
10
|
import { DeviceManager } from './deviceManager.js';
|
|
13
11
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
@@ -35,7 +33,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
35
33
|
totalMemory: '',
|
|
36
34
|
freeMemory: '',
|
|
37
35
|
systemUptime: '',
|
|
38
|
-
|
|
36
|
+
processUptime: '',
|
|
37
|
+
cpuUsage: '',
|
|
39
38
|
rss: '',
|
|
40
39
|
heapTotal: '',
|
|
41
40
|
heapUsed: '',
|
|
@@ -85,6 +84,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
85
84
|
bridgeMode = '';
|
|
86
85
|
restartMode = '';
|
|
87
86
|
profile = getParameter('profile');
|
|
87
|
+
shutdown = false;
|
|
88
88
|
edge = true;
|
|
89
89
|
log;
|
|
90
90
|
matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
@@ -126,6 +126,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
126
126
|
constructor() {
|
|
127
127
|
super();
|
|
128
128
|
}
|
|
129
|
+
emit(eventName, ...args) {
|
|
130
|
+
return super.emit(eventName, ...args);
|
|
131
|
+
}
|
|
132
|
+
on(eventName, listener) {
|
|
133
|
+
return super.on(eventName, listener);
|
|
134
|
+
}
|
|
129
135
|
getDevices() {
|
|
130
136
|
return this.devices.array();
|
|
131
137
|
}
|
|
@@ -167,6 +173,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
167
173
|
return Matterbridge.instance;
|
|
168
174
|
}
|
|
169
175
|
async destroyInstance() {
|
|
176
|
+
this.log.info(`Destroy instance...`);
|
|
170
177
|
const servers = [];
|
|
171
178
|
if (this.bridgeMode === 'bridge') {
|
|
172
179
|
if (this.serverNode)
|
|
@@ -179,6 +186,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
188
|
await this.cleanup('destroying instance...', false);
|
|
189
|
+
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
182
190
|
for (const server of servers) {
|
|
183
191
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
184
192
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
@@ -436,7 +444,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
436
444
|
- disable [plugin name]: disable the globally installed plugin with the given name
|
|
437
445
|
- reset [plugin path]: remove the commissioning for the plugin from the given absolute or relative path (childbridge mode). Shutdown Matterbridge before using it!
|
|
438
446
|
- reset [plugin name]: remove the commissioning for the globally installed plugin (childbridge mode). Shutdown Matterbridge before using it!${rs}`);
|
|
439
|
-
this.
|
|
447
|
+
this.shutdown = true;
|
|
440
448
|
return;
|
|
441
449
|
}
|
|
442
450
|
if (hasParameter('list')) {
|
|
@@ -465,7 +473,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
465
473
|
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
466
474
|
}
|
|
467
475
|
});
|
|
468
|
-
this.
|
|
476
|
+
this.shutdown = true;
|
|
469
477
|
return;
|
|
470
478
|
}
|
|
471
479
|
if (hasParameter('logstorage')) {
|
|
@@ -475,41 +483,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
475
483
|
this.log.info(`${plg}${plugin.name}${nf} storage log`);
|
|
476
484
|
await plugin.nodeContext?.logStorage();
|
|
477
485
|
}
|
|
478
|
-
this.
|
|
486
|
+
this.shutdown = true;
|
|
479
487
|
return;
|
|
480
488
|
}
|
|
481
489
|
if (hasParameter('loginterfaces')) {
|
|
482
490
|
this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
|
|
483
491
|
logInterfaces();
|
|
484
|
-
this.
|
|
492
|
+
this.shutdown = true;
|
|
485
493
|
return;
|
|
486
494
|
}
|
|
487
495
|
if (getParameter('add')) {
|
|
488
496
|
this.log.debug(`Adding plugin ${getParameter('add')}`);
|
|
489
497
|
await this.plugins.add(getParameter('add'));
|
|
490
|
-
this.
|
|
498
|
+
this.shutdown = true;
|
|
491
499
|
return;
|
|
492
500
|
}
|
|
493
501
|
if (getParameter('remove')) {
|
|
494
502
|
this.log.debug(`Removing plugin ${getParameter('remove')}`);
|
|
495
503
|
await this.plugins.remove(getParameter('remove'));
|
|
496
|
-
this.
|
|
504
|
+
this.shutdown = true;
|
|
497
505
|
return;
|
|
498
506
|
}
|
|
499
507
|
if (getParameter('enable')) {
|
|
500
508
|
this.log.debug(`Enabling plugin ${getParameter('enable')}`);
|
|
501
509
|
await this.plugins.enable(getParameter('enable'));
|
|
502
|
-
this.
|
|
510
|
+
this.shutdown = true;
|
|
503
511
|
return;
|
|
504
512
|
}
|
|
505
513
|
if (getParameter('disable')) {
|
|
506
514
|
this.log.debug(`Disabling plugin ${getParameter('disable')}`);
|
|
507
515
|
await this.plugins.disable(getParameter('disable'));
|
|
508
|
-
this.
|
|
516
|
+
this.shutdown = true;
|
|
509
517
|
return;
|
|
510
518
|
}
|
|
511
519
|
if (hasParameter('factoryreset')) {
|
|
512
520
|
await this.shutdownProcessAndFactoryReset();
|
|
521
|
+
this.shutdown = true;
|
|
513
522
|
return;
|
|
514
523
|
}
|
|
515
524
|
try {
|
|
@@ -521,6 +530,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
521
530
|
}
|
|
522
531
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
523
532
|
await this.shutdownProcessAndReset();
|
|
533
|
+
this.shutdown = true;
|
|
524
534
|
return;
|
|
525
535
|
}
|
|
526
536
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
@@ -528,20 +538,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
528
538
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
529
539
|
if (plugin) {
|
|
530
540
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
531
|
-
if (!matterStorageManager)
|
|
541
|
+
if (!matterStorageManager) {
|
|
532
542
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
await matterStorageManager?.createContext('events')?.clearAll();
|
|
546
|
+
await matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
547
|
+
await matterStorageManager?.createContext('root')?.clearAll();
|
|
548
|
+
await matterStorageManager?.createContext('sessions')?.clearAll();
|
|
549
|
+
await matterStorageManager?.createContext('persist')?.clearAll();
|
|
550
|
+
this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
|
|
551
|
+
}
|
|
539
552
|
}
|
|
540
553
|
else {
|
|
541
554
|
this.log.warn(`Plugin ${plg}${getParameter('reset')}${wr} not registerd in matterbridge`);
|
|
542
555
|
}
|
|
543
556
|
await this.stopMatterStorage();
|
|
544
|
-
this.
|
|
557
|
+
this.shutdown = true;
|
|
545
558
|
return;
|
|
546
559
|
}
|
|
547
560
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
@@ -713,6 +726,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
713
726
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
714
727
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
715
728
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
729
|
+
const { fileURLToPath } = await import('node:url');
|
|
716
730
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
717
731
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
718
732
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
@@ -721,7 +735,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
721
735
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
722
736
|
if (this.globalModulesDirectory === '') {
|
|
723
737
|
try {
|
|
724
|
-
this.
|
|
738
|
+
this.execRunningCount++;
|
|
739
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
740
|
+
this.execRunningCount--;
|
|
725
741
|
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
726
742
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
727
743
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
@@ -790,20 +806,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
790
806
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
791
807
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
792
808
|
}
|
|
793
|
-
async getGlobalNodeModules() {
|
|
794
|
-
return new Promise((resolve, reject) => {
|
|
795
|
-
this.execRunningCount++;
|
|
796
|
-
exec('npm root -g', (error, stdout) => {
|
|
797
|
-
this.execRunningCount--;
|
|
798
|
-
if (error) {
|
|
799
|
-
reject(error);
|
|
800
|
-
}
|
|
801
|
-
else {
|
|
802
|
-
resolve(stdout.trim());
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
809
|
async getMatterbridgeLatestVersion() {
|
|
808
810
|
getNpmPackageVersion('matterbridge')
|
|
809
811
|
.then(async (version) => {
|
|
@@ -1195,11 +1197,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1195
1197
|
if (!plugin.enabled)
|
|
1196
1198
|
continue;
|
|
1197
1199
|
if (plugin.type === 'DynamicPlatform') {
|
|
1198
|
-
plugin
|
|
1199
|
-
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1200
|
-
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1201
|
-
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1202
|
-
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1200
|
+
await this.createDynamicPlugin(plugin);
|
|
1203
1201
|
}
|
|
1204
1202
|
}
|
|
1205
1203
|
await this.startPlugins();
|
|
@@ -1252,7 +1250,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1252
1250
|
for (const plugin of this.plugins) {
|
|
1253
1251
|
if (!plugin.enabled || plugin.error)
|
|
1254
1252
|
continue;
|
|
1255
|
-
if (!plugin.addedDevices || plugin.addedDevices === 0) {
|
|
1253
|
+
if (plugin.type !== 'DynamicPlatform' && (!plugin.addedDevices || plugin.addedDevices === 0)) {
|
|
1256
1254
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1257
1255
|
continue;
|
|
1258
1256
|
}
|
|
@@ -1308,6 +1306,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1308
1306
|
this.log.info('Matter node storage closed');
|
|
1309
1307
|
}
|
|
1310
1308
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1309
|
+
const { randomBytes } = await import('node:crypto');
|
|
1311
1310
|
if (!this.matterStorageService)
|
|
1312
1311
|
throw new Error('No storage service initialized');
|
|
1313
1312
|
this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
|
|
@@ -1550,13 +1549,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1550
1549
|
}
|
|
1551
1550
|
}
|
|
1552
1551
|
async advertiseServerNode(matterServerNode) {
|
|
1553
|
-
if (matterServerNode
|
|
1552
|
+
if (matterServerNode) {
|
|
1554
1553
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
1555
1554
|
const { qrPairingCode, manualPairingCode } = matterServerNode.state.commissioning.pairingCodes;
|
|
1556
1555
|
this.log.notice(`Advertising for ${matterServerNode.id} is now started with the following pairing codes: qrPairingCode ${qrPairingCode}, manualPairingCode ${manualPairingCode}`);
|
|
1557
1556
|
return { qrPairingCode, manualPairingCode };
|
|
1558
1557
|
}
|
|
1559
|
-
|
|
1558
|
+
}
|
|
1559
|
+
async stopAdvertiseServerNode(matterServerNode) {
|
|
1560
|
+
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1561
|
+
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1562
|
+
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1563
|
+
}
|
|
1560
1564
|
}
|
|
1561
1565
|
async createAggregatorNode(storageContext) {
|
|
1562
1566
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
@@ -1628,15 +1632,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1628
1632
|
plugin.registeredDevices--;
|
|
1629
1633
|
if (plugin.addedDevices !== undefined)
|
|
1630
1634
|
plugin.addedDevices--;
|
|
1631
|
-
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1632
|
-
if (plugin.serverNode) {
|
|
1633
|
-
await this.stopServerNode(plugin.serverNode);
|
|
1634
|
-
plugin.locked = false;
|
|
1635
|
-
plugin.aggregatorNode = undefined;
|
|
1636
|
-
plugin.serverNode = undefined;
|
|
1637
|
-
this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
1635
|
}
|
|
1641
1636
|
this.devices.remove(device);
|
|
1642
1637
|
}
|
|
@@ -1695,7 +1690,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1695
1690
|
getVendorIdName = (vendorId) => {
|
|
1696
1691
|
if (!vendorId)
|
|
1697
1692
|
return '';
|
|
1698
|
-
let vendorName = '';
|
|
1693
|
+
let vendorName = '(Unknown vendorId)';
|
|
1699
1694
|
switch (vendorId) {
|
|
1700
1695
|
case 4937:
|
|
1701
1696
|
vendorName = '(AppleHome)';
|
|
@@ -1725,15 +1720,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1725
1720
|
vendorName = '(eWeLink)';
|
|
1726
1721
|
break;
|
|
1727
1722
|
case 65521:
|
|
1728
|
-
vendorName = '(
|
|
1729
|
-
break;
|
|
1730
|
-
default:
|
|
1731
|
-
vendorName = '(unknown)';
|
|
1723
|
+
vendorName = '(MatterServer)';
|
|
1732
1724
|
break;
|
|
1733
1725
|
}
|
|
1734
1726
|
return vendorName;
|
|
1735
1727
|
};
|
|
1736
1728
|
async spawnCommand(command, args = []) {
|
|
1729
|
+
const { spawn } = await import('node:child_process');
|
|
1737
1730
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1738
1731
|
if (process.platform === 'win32' && command === 'npm') {
|
|
1739
1732
|
const argstring = 'npm ' + args.join(' ');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } from './logger/export.js';
|
|
2
2
|
import { bridgedNode } from './matterbridgeDeviceTypes.js';
|
|
3
|
-
import { isValidNumber, isValidObject } from './utils/
|
|
3
|
+
import { isValidNumber, isValidObject } from './utils/export.js';
|
|
4
4
|
import { MatterbridgeBehavior, MatterbridgeBehaviorDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, } from './matterbridgeBehaviors.js';
|
|
5
5
|
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, } from './matterbridgeEndpointHelpers.js';
|
|
6
6
|
import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, VendorId } from '@matter/main';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
2
|
import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from './logger/export.js';
|
|
3
|
+
import { deepCopy, deepEqual, isValidArray } from './utils/export.js';
|
|
3
4
|
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, } from './matterbridgeBehaviors.js';
|
|
4
5
|
import { Lifecycle } from '@matter/main';
|
|
5
6
|
import { getClusterNameById } from '@matter/main/types';
|
|
@@ -73,7 +74,6 @@ import { Pm25ConcentrationMeasurementServer } from '@matter/main/behaviors/pm25-
|
|
|
73
74
|
import { Pm10ConcentrationMeasurementServer } from '@matter/main/behaviors/pm10-concentration-measurement';
|
|
74
75
|
import { RadonConcentrationMeasurementServer } from '@matter/main/behaviors/radon-concentration-measurement';
|
|
75
76
|
import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/main/behaviors/total-volatile-organic-compounds-concentration-measurement';
|
|
76
|
-
import { deepCopy, deepEqual, isValidArray } from './utils/utils.js';
|
|
77
77
|
export function capitalizeFirstLetter(name) {
|
|
78
78
|
if (!name)
|
|
79
79
|
return name;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { checkNotLatinCharacters } from './matterbridgeEndpointHelpers.js';
|
|
2
|
-
import { isValidArray, isValidObject, isValidString } from './utils/
|
|
2
|
+
import { isValidArray, isValidObject, isValidString } from './utils/export.js';
|
|
3
3
|
import { CYAN, db, er, nf, wr } from './logger/export.js';
|
|
4
4
|
import { NodeStorageManager } from './storage/export.js';
|
|
5
|
-
import path from 'path';
|
|
5
|
+
import path from 'node:path';
|
|
6
6
|
export class MatterbridgePlatform {
|
|
7
7
|
matterbridge;
|
|
8
8
|
log;
|
package/dist/pluginManager.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { AnsiLogger, BLUE, db, er, nf, nt, rs,
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { promises as fs } from 'fs';
|
|
4
|
-
import { pathToFileURL } from 'url';
|
|
5
|
-
import { exec } from 'child_process';
|
|
1
|
+
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from './logger/export.js';
|
|
6
2
|
import { plg, typ } from './matterbridgeTypes.js';
|
|
7
|
-
import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js';
|
|
8
3
|
export class PluginManager {
|
|
9
4
|
_plugins = new Map();
|
|
10
5
|
nodeContext;
|
|
@@ -84,12 +79,14 @@ export class PluginManager {
|
|
|
84
79
|
return plugins.length;
|
|
85
80
|
}
|
|
86
81
|
async resolve(pluginPath) {
|
|
82
|
+
const { default: path } = await import('node:path');
|
|
83
|
+
const { promises } = await import('node:fs');
|
|
87
84
|
if (!pluginPath.endsWith('package.json'))
|
|
88
85
|
pluginPath = path.join(pluginPath, 'package.json');
|
|
89
86
|
let packageJsonPath = path.resolve(pluginPath);
|
|
90
87
|
this.log.debug(`Resolving plugin path ${plg}${packageJsonPath}${db}`);
|
|
91
88
|
try {
|
|
92
|
-
await
|
|
89
|
+
await promises.access(packageJsonPath);
|
|
93
90
|
}
|
|
94
91
|
catch {
|
|
95
92
|
this.log.debug(`Package.json not found at ${plg}${packageJsonPath}${db}`);
|
|
@@ -97,7 +94,7 @@ export class PluginManager {
|
|
|
97
94
|
this.log.debug(`Trying at ${plg}${packageJsonPath}${db}`);
|
|
98
95
|
}
|
|
99
96
|
try {
|
|
100
|
-
const packageJson = JSON.parse(await
|
|
97
|
+
const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
|
|
101
98
|
if (!packageJson.name) {
|
|
102
99
|
this.log.error(`Package.json name not found at ${packageJsonPath}`);
|
|
103
100
|
return null;
|
|
@@ -161,9 +158,10 @@ export class PluginManager {
|
|
|
161
158
|
}
|
|
162
159
|
}
|
|
163
160
|
async parse(plugin) {
|
|
164
|
-
|
|
161
|
+
const { promises } = await import('node:fs');
|
|
165
162
|
try {
|
|
166
|
-
|
|
163
|
+
this.log.debug(`Parsing package.json of plugin ${plg}${plugin.name}${db}`);
|
|
164
|
+
const packageJson = JSON.parse(await promises.readFile(plugin.path, 'utf8'));
|
|
167
165
|
if (!packageJson.name)
|
|
168
166
|
this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no name in package.json`);
|
|
169
167
|
if (!packageJson.version)
|
|
@@ -235,6 +233,7 @@ export class PluginManager {
|
|
|
235
233
|
}
|
|
236
234
|
}
|
|
237
235
|
async enable(nameOrPath) {
|
|
236
|
+
const { promises } = await import('node:fs');
|
|
238
237
|
if (!nameOrPath || nameOrPath === '')
|
|
239
238
|
return null;
|
|
240
239
|
if (this._plugins.has(nameOrPath)) {
|
|
@@ -250,7 +249,7 @@ export class PluginManager {
|
|
|
250
249
|
return null;
|
|
251
250
|
}
|
|
252
251
|
try {
|
|
253
|
-
const packageJson = JSON.parse(await
|
|
252
|
+
const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
|
|
254
253
|
const plugin = this._plugins.get(packageJson.name);
|
|
255
254
|
if (!plugin) {
|
|
256
255
|
this.log.error(`Failed to enable plugin ${plg}${nameOrPath}${er}: plugin not registered`);
|
|
@@ -267,6 +266,7 @@ export class PluginManager {
|
|
|
267
266
|
}
|
|
268
267
|
}
|
|
269
268
|
async disable(nameOrPath) {
|
|
269
|
+
const { promises } = await import('node:fs');
|
|
270
270
|
if (!nameOrPath || nameOrPath === '')
|
|
271
271
|
return null;
|
|
272
272
|
if (this._plugins.has(nameOrPath)) {
|
|
@@ -282,7 +282,7 @@ export class PluginManager {
|
|
|
282
282
|
return null;
|
|
283
283
|
}
|
|
284
284
|
try {
|
|
285
|
-
const packageJson = JSON.parse(await
|
|
285
|
+
const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
|
|
286
286
|
const plugin = this._plugins.get(packageJson.name);
|
|
287
287
|
if (!plugin) {
|
|
288
288
|
this.log.error(`Failed to disable plugin ${plg}${nameOrPath}${er}: plugin not registered`);
|
|
@@ -299,6 +299,7 @@ export class PluginManager {
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
async remove(nameOrPath) {
|
|
302
|
+
const { promises } = await import('node:fs');
|
|
302
303
|
if (!nameOrPath || nameOrPath === '')
|
|
303
304
|
return null;
|
|
304
305
|
if (this._plugins.has(nameOrPath)) {
|
|
@@ -314,7 +315,7 @@ export class PluginManager {
|
|
|
314
315
|
return null;
|
|
315
316
|
}
|
|
316
317
|
try {
|
|
317
|
-
const packageJson = JSON.parse(await
|
|
318
|
+
const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
|
|
318
319
|
const plugin = this._plugins.get(packageJson.name);
|
|
319
320
|
if (!plugin) {
|
|
320
321
|
this.log.error(`Failed to remove plugin ${plg}${nameOrPath}${er}: plugin not registered`);
|
|
@@ -331,6 +332,7 @@ export class PluginManager {
|
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
334
|
async add(nameOrPath) {
|
|
335
|
+
const { promises } = await import('node:fs');
|
|
334
336
|
if (!nameOrPath || nameOrPath === '')
|
|
335
337
|
return null;
|
|
336
338
|
const packageJsonPath = await this.resolve(nameOrPath);
|
|
@@ -339,7 +341,7 @@ export class PluginManager {
|
|
|
339
341
|
return null;
|
|
340
342
|
}
|
|
341
343
|
try {
|
|
342
|
-
const packageJson = JSON.parse(await
|
|
344
|
+
const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
|
|
343
345
|
if (this._plugins.get(packageJson.name)) {
|
|
344
346
|
this.log.info(`Plugin ${plg}${nameOrPath}${nf} already registered`);
|
|
345
347
|
return null;
|
|
@@ -351,15 +353,16 @@ export class PluginManager {
|
|
|
351
353
|
return plugin || null;
|
|
352
354
|
}
|
|
353
355
|
catch (err) {
|
|
354
|
-
this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err}`);
|
|
356
|
+
this.log.error(`Failed to parse package.json of plugin ${plg}${nameOrPath}${er}: ${err instanceof Error ? err.message : err}`);
|
|
355
357
|
return null;
|
|
356
358
|
}
|
|
357
359
|
}
|
|
358
360
|
async install(name) {
|
|
361
|
+
const { exec } = await import('node:child_process');
|
|
359
362
|
await this.uninstall(name);
|
|
360
363
|
this.log.info(`Installing plugin ${plg}${name}${nf}`);
|
|
361
|
-
return new Promise((resolve
|
|
362
|
-
exec(`npm install -g ${name} --omit=dev
|
|
364
|
+
return new Promise((resolve) => {
|
|
365
|
+
exec(`npm install -g ${name} --omit=dev`, (error, stdout, stderr) => {
|
|
363
366
|
if (error) {
|
|
364
367
|
this.log.error(`Failed to install plugin ${plg}${name}${er}: ${error}`);
|
|
365
368
|
this.log.debug(`Failed to install plugin ${plg}${name}${db}: ${stderr}`);
|
|
@@ -389,9 +392,10 @@ export class PluginManager {
|
|
|
389
392
|
});
|
|
390
393
|
}
|
|
391
394
|
async uninstall(name) {
|
|
395
|
+
const { exec } = await import('node:child_process');
|
|
392
396
|
this.log.info(`Uninstalling plugin ${plg}${name}${nf}`);
|
|
393
|
-
return new Promise((resolve
|
|
394
|
-
exec(`npm uninstall -g ${name}
|
|
397
|
+
return new Promise((resolve) => {
|
|
398
|
+
exec(`npm uninstall -g ${name}`, (error, stdout, stderr) => {
|
|
395
399
|
if (error) {
|
|
396
400
|
this.log.error(`Failed to uninstall plugin ${plg}${name}${er}: ${error}`);
|
|
397
401
|
this.log.debug(`Failed to uninstall plugin ${plg}${name}${db}: ${stderr}`);
|
|
@@ -406,6 +410,8 @@ export class PluginManager {
|
|
|
406
410
|
});
|
|
407
411
|
}
|
|
408
412
|
async load(plugin, start = false, message = '', configure = false) {
|
|
413
|
+
const { promises } = await import('node:fs');
|
|
414
|
+
const { default: path } = await import('node:path');
|
|
409
415
|
if (!plugin.enabled) {
|
|
410
416
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
|
|
411
417
|
return undefined;
|
|
@@ -416,8 +422,9 @@ export class PluginManager {
|
|
|
416
422
|
}
|
|
417
423
|
this.log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
|
|
418
424
|
try {
|
|
419
|
-
const packageJson = JSON.parse(await
|
|
425
|
+
const packageJson = JSON.parse(await promises.readFile(plugin.path, 'utf8'));
|
|
420
426
|
const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
|
|
427
|
+
const { pathToFileURL } = await import('node:url');
|
|
421
428
|
const pluginUrl = pathToFileURL(pluginEntry);
|
|
422
429
|
this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
|
|
423
430
|
const pluginInstance = await import(pluginUrl.href);
|
|
@@ -570,10 +577,13 @@ export class PluginManager {
|
|
|
570
577
|
return undefined;
|
|
571
578
|
}
|
|
572
579
|
async loadConfig(plugin) {
|
|
580
|
+
const { default: path } = await import('node:path');
|
|
581
|
+
const { promises } = await import('node:fs');
|
|
582
|
+
const { shelly_config, somfytahoma_config, zigbee2mqtt_config } = await import('./defaultConfigSchema.js');
|
|
573
583
|
const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
|
|
574
584
|
try {
|
|
575
|
-
await
|
|
576
|
-
const data = await
|
|
585
|
+
await promises.access(configFile);
|
|
586
|
+
const data = await promises.readFile(configFile, 'utf8');
|
|
577
587
|
const config = JSON.parse(data);
|
|
578
588
|
this.log.debug(`Loaded config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
|
|
579
589
|
config.name = plugin.name;
|
|
@@ -597,7 +607,7 @@ export class PluginManager {
|
|
|
597
607
|
else
|
|
598
608
|
config = { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
|
|
599
609
|
try {
|
|
600
|
-
await
|
|
610
|
+
await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
601
611
|
this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
|
|
602
612
|
return config;
|
|
603
613
|
}
|
|
@@ -613,13 +623,15 @@ export class PluginManager {
|
|
|
613
623
|
}
|
|
614
624
|
}
|
|
615
625
|
async saveConfigFromPlugin(plugin) {
|
|
626
|
+
const { default: path } = await import('node:path');
|
|
627
|
+
const { promises } = await import('node:fs');
|
|
616
628
|
if (!plugin.platform?.config) {
|
|
617
629
|
this.log.error(`Error saving config file for plugin ${plg}${plugin.name}${er}: config not found`);
|
|
618
630
|
return Promise.reject(new Error(`Error saving config file for plugin ${plg}${plugin.name}${er}: config not found`));
|
|
619
631
|
}
|
|
620
632
|
const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
|
|
621
633
|
try {
|
|
622
|
-
await
|
|
634
|
+
await promises.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2), 'utf8');
|
|
623
635
|
this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
|
|
624
636
|
return Promise.resolve();
|
|
625
637
|
}
|
|
@@ -629,13 +641,15 @@ export class PluginManager {
|
|
|
629
641
|
}
|
|
630
642
|
}
|
|
631
643
|
async saveConfigFromJson(plugin, config) {
|
|
644
|
+
const { default: path } = await import('node:path');
|
|
645
|
+
const { promises } = await import('node:fs');
|
|
632
646
|
if (!config.name || !config.type || config.name !== plugin.name) {
|
|
633
647
|
this.log.error(`Error saving config file for plugin ${plg}${plugin.name}${er}. Wrong config data content:${rs}\n`, config);
|
|
634
648
|
return;
|
|
635
649
|
}
|
|
636
650
|
const configFile = path.join(this.matterbridge.matterbridgeDirectory, `${plugin.name}.config.json`);
|
|
637
651
|
try {
|
|
638
|
-
await
|
|
652
|
+
await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
639
653
|
plugin.configJson = config;
|
|
640
654
|
this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
|
|
641
655
|
}
|
|
@@ -645,10 +659,11 @@ export class PluginManager {
|
|
|
645
659
|
}
|
|
646
660
|
}
|
|
647
661
|
async loadSchema(plugin) {
|
|
662
|
+
const { promises } = await import('node:fs');
|
|
648
663
|
const schemaFile = plugin.path.replace('package.json', `${plugin.name}.schema.json`);
|
|
649
664
|
try {
|
|
650
|
-
await
|
|
651
|
-
const data = await
|
|
665
|
+
await promises.access(schemaFile);
|
|
666
|
+
const data = await promises.readFile(schemaFile, 'utf8');
|
|
652
667
|
const schema = JSON.parse(data);
|
|
653
668
|
schema.title = plugin.description;
|
|
654
669
|
schema.description = plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author;
|
package/dist/utils/colorUtils.js
CHANGED
package/dist/utils/export.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function isValidIpv4Address(ipv4Address) {
|
|
2
|
+
const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
3
|
+
return ipv4Regex.test(ipv4Address);
|
|
4
|
+
}
|
|
5
|
+
export function isValidNumber(value, min, max) {
|
|
6
|
+
if (value === undefined || value === null || typeof value !== 'number' || Number.isNaN(value))
|
|
7
|
+
return false;
|
|
8
|
+
if (min !== undefined && value < min)
|
|
9
|
+
return false;
|
|
10
|
+
if (max !== undefined && value > max)
|
|
11
|
+
return false;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
export function isValidBoolean(value) {
|
|
15
|
+
return value !== undefined && value !== null && typeof value === 'boolean';
|
|
16
|
+
}
|
|
17
|
+
export function isValidString(value, minLength, maxLength) {
|
|
18
|
+
if (value === undefined || value === null || typeof value !== 'string')
|
|
19
|
+
return false;
|
|
20
|
+
if (minLength !== undefined && value.length < minLength)
|
|
21
|
+
return false;
|
|
22
|
+
if (maxLength !== undefined && value.length > maxLength)
|
|
23
|
+
return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
export function isValidObject(value, minLength, maxLength) {
|
|
27
|
+
if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value))
|
|
28
|
+
return false;
|
|
29
|
+
const keys = Object.keys(value);
|
|
30
|
+
if (minLength !== undefined && keys.length < minLength)
|
|
31
|
+
return false;
|
|
32
|
+
if (maxLength !== undefined && keys.length > maxLength)
|
|
33
|
+
return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
export function isValidArray(value, minLength, maxLength) {
|
|
37
|
+
if (value === undefined || value === null || !Array.isArray(value))
|
|
38
|
+
return false;
|
|
39
|
+
if (minLength !== undefined && value.length < minLength)
|
|
40
|
+
return false;
|
|
41
|
+
if (maxLength !== undefined && value.length > maxLength)
|
|
42
|
+
return false;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
export function isValidNull(value) {
|
|
46
|
+
return value === null;
|
|
47
|
+
}
|
|
48
|
+
export function isValidUndefined(value) {
|
|
49
|
+
return value === undefined;
|
|
50
|
+
}
|