matterbridge 3.1.8-dev-20250725-d6e57e3 → 3.1.8-dev-20250725-aa6ff9e
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 +8 -2
- package/dist/frontend.js +34 -8
- package/dist/matterbridgeEndpoint.js +6 -4
- package/dist/utils/hex.js +91 -0
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.1d25e0d8.js → main.bb47a7dc.js} +3 -3
- package/frontend/build/static/js/{main.1d25e0d8.js.map → main.bb47a7dc.js.map} +1 -1
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- /package/frontend/build/static/js/{main.1d25e0d8.js.LICENSE.txt → main.bb47a7dc.js.LICENSE.txt} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,13 +12,19 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
12
12
|
|
|
13
13
|
### Added
|
|
14
14
|
|
|
15
|
-
- [certification]: Improved certification management in pairing.json.
|
|
16
|
-
- [workflow]: Update permissions and change GitHub token for Docker build
|
|
15
|
+
- [certification]: Improved certification management in pairing.json. Added pemToBuffer function for converting PEM strings to Uint8Array.
|
|
16
|
+
- [workflow]: Update permissions and change GitHub token for Docker build triggers.
|
|
17
|
+
- [frontend]: Added Changelog button when a new version is installed.
|
|
18
|
+
- [frontend]: Added restart plugin in childbridge mode.
|
|
17
19
|
|
|
18
20
|
### Changed
|
|
19
21
|
|
|
20
22
|
- [package]: Updated dependencies.
|
|
21
23
|
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- [switch]: Added conditional handling for momentary switch events in MatterbridgeEndpoint for single press only switches.
|
|
27
|
+
|
|
22
28
|
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
23
29
|
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
24
30
|
</a>
|
package/dist/frontend.js
CHANGED
|
@@ -315,7 +315,7 @@ export class Frontend extends EventEmitter {
|
|
|
315
315
|
});
|
|
316
316
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
317
317
|
this.log.debug('The frontend sent /api/plugins');
|
|
318
|
-
res.json(this.
|
|
318
|
+
res.json(this.getPlugins());
|
|
319
319
|
});
|
|
320
320
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
321
321
|
this.log.debug('The frontend sent /api/devices');
|
|
@@ -783,7 +783,7 @@ export class Frontend extends EventEmitter {
|
|
|
783
783
|
});
|
|
784
784
|
return attributes.trimStart().trimEnd();
|
|
785
785
|
}
|
|
786
|
-
|
|
786
|
+
getPlugins() {
|
|
787
787
|
if (this.matterbridge.hasCleanupStarted)
|
|
788
788
|
return [];
|
|
789
789
|
const baseRegisteredPlugins = [];
|
|
@@ -814,11 +814,11 @@ export class Frontend extends EventEmitter {
|
|
|
814
814
|
schemaJson: plugin.schemaJson,
|
|
815
815
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
816
816
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
817
|
-
paired: plugin.serverNode
|
|
818
|
-
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode
|
|
819
|
-
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode
|
|
820
|
-
fabricInformations: plugin.serverNode ? this.matterbridge.sanitizeFabricInformations(Object.values(plugin.serverNode
|
|
821
|
-
sessionInformations: plugin.serverNode ? this.matterbridge.sanitizeSessionInformation(Object.values(plugin.serverNode
|
|
817
|
+
paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
|
|
818
|
+
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
|
|
819
|
+
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
|
|
820
|
+
fabricInformations: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? this.matterbridge.sanitizeFabricInformations(Object.values(plugin.serverNode.state.commissioning.fabrics)) : undefined,
|
|
821
|
+
sessionInformations: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? this.matterbridge.sanitizeSessionInformation(Object.values(plugin.serverNode.state.sessions.sessions)) : undefined,
|
|
822
822
|
});
|
|
823
823
|
}
|
|
824
824
|
return baseRegisteredPlugins;
|
|
@@ -1112,6 +1112,32 @@ export class Frontend extends EventEmitter {
|
|
|
1112
1112
|
this.wssSendRefreshRequired('devices');
|
|
1113
1113
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
1114
1114
|
}
|
|
1115
|
+
else if (data.method === '/api/restartplugin') {
|
|
1116
|
+
if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
|
|
1117
|
+
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/restartplugin' }));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1121
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1122
|
+
if (plugin.serverNode) {
|
|
1123
|
+
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1124
|
+
plugin.serverNode = undefined;
|
|
1125
|
+
}
|
|
1126
|
+
for (const device of this.matterbridge.devices) {
|
|
1127
|
+
if (device.plugin === plugin.name) {
|
|
1128
|
+
this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
|
|
1129
|
+
this.matterbridge.devices.remove(device);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1133
|
+
if (plugin.serverNode) {
|
|
1134
|
+
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1135
|
+
}
|
|
1136
|
+
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
1137
|
+
this.wssSendRefreshRequired('plugins');
|
|
1138
|
+
this.wssSendRefreshRequired('devices');
|
|
1139
|
+
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
1140
|
+
}
|
|
1115
1141
|
else if (data.method === '/api/savepluginconfig') {
|
|
1116
1142
|
if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
|
|
1117
1143
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/savepluginconfig' }));
|
|
@@ -1220,7 +1246,7 @@ export class Frontend extends EventEmitter {
|
|
|
1220
1246
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: await this.getApiSettings() }));
|
|
1221
1247
|
}
|
|
1222
1248
|
else if (data.method === '/api/plugins') {
|
|
1223
|
-
const response = this.
|
|
1249
|
+
const response = this.getPlugins();
|
|
1224
1250
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response }));
|
|
1225
1251
|
}
|
|
1226
1252
|
else if (data.method === '/api/devices') {
|
|
@@ -61,7 +61,7 @@ import { AnsiLogger, CYAN, YELLOW, db, debugStringify, hk, or, zb } from './logg
|
|
|
61
61
|
import { bridgedNode } from './matterbridgeDeviceTypes.js';
|
|
62
62
|
import { isValidNumber, isValidObject, isValidString } from './utils/export.js';
|
|
63
63
|
import { MatterbridgeServer, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeLiftTiltWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeHepaFilterMonitoringServer, } from './matterbridgeBehaviors.js';
|
|
64
|
-
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, } from './matterbridgeEndpointHelpers.js';
|
|
64
|
+
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, } from './matterbridgeEndpointHelpers.js';
|
|
65
65
|
export class MatterbridgeEndpoint extends Endpoint {
|
|
66
66
|
static bridgeMode = '';
|
|
67
67
|
static logLevel = "info";
|
|
@@ -1027,9 +1027,11 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
1027
1027
|
await this.setAttribute(Switch.Cluster.id, 'currentPosition', 1, log);
|
|
1028
1028
|
await this.triggerEvent(Switch.Cluster.id, 'initialPress', { newPosition: 1 }, log);
|
|
1029
1029
|
await this.setAttribute(Switch.Cluster.id, 'currentPosition', 0, log);
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1030
|
+
if (featuresFor(this, 'Switch').momentarySwitchRelease) {
|
|
1031
|
+
await this.triggerEvent(Switch.Cluster.id, 'shortRelease', { previousPosition: 1 }, log);
|
|
1032
|
+
await this.setAttribute(Switch.Cluster.id, 'currentPosition', 0, log);
|
|
1033
|
+
await this.triggerEvent(Switch.Cluster.id, 'multiPressComplete', { previousPosition: 1, totalNumberOfPressesCounted: 1 }, log);
|
|
1034
|
+
}
|
|
1033
1035
|
}
|
|
1034
1036
|
if (event === 'Double') {
|
|
1035
1037
|
log?.info(`${db}Trigger endpoint ${or}${this.id}:${this.number}${db} event ${hk}Switch.DoublePress${db}`);
|
package/dist/utils/hex.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createPublicKey, createPrivateKey, X509Certificate } from 'node:crypto';
|
|
1
2
|
export function bufferToHex(buffer) {
|
|
2
3
|
if (!(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer))) {
|
|
3
4
|
throw new TypeError('Expected input to be an ArrayBuffer or ArrayBufferView');
|
|
@@ -25,3 +26,93 @@ export function hexToBuffer(hex) {
|
|
|
25
26
|
}
|
|
26
27
|
return result;
|
|
27
28
|
}
|
|
29
|
+
export function pemToBuffer(pem, validate = false) {
|
|
30
|
+
if (typeof pem !== 'string') {
|
|
31
|
+
throw new TypeError('Expected a string for PEM input');
|
|
32
|
+
}
|
|
33
|
+
const cleaned = pem.trim();
|
|
34
|
+
if (!cleaned.includes('-----BEGIN') || !cleaned.includes('-----END')) {
|
|
35
|
+
throw new Error('Invalid PEM format: missing BEGIN/END markers');
|
|
36
|
+
}
|
|
37
|
+
const lines = cleaned.split('\n');
|
|
38
|
+
const base64Lines = [];
|
|
39
|
+
let inContent = false;
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const trimmedLine = line.trim();
|
|
42
|
+
if (trimmedLine.startsWith('-----BEGIN')) {
|
|
43
|
+
inContent = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (trimmedLine.startsWith('-----END')) {
|
|
47
|
+
inContent = false;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (inContent && trimmedLine.length > 0) {
|
|
51
|
+
base64Lines.push(trimmedLine);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (base64Lines.length === 0) {
|
|
55
|
+
throw new Error('Invalid PEM format: no content found between BEGIN/END markers');
|
|
56
|
+
}
|
|
57
|
+
const base64String = base64Lines.join('');
|
|
58
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64String)) {
|
|
59
|
+
throw new Error('Invalid PEM format: contains invalid base64 characters');
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const buffer = Buffer.from(base64String, 'base64');
|
|
63
|
+
const result = new Uint8Array(buffer);
|
|
64
|
+
if (validate) {
|
|
65
|
+
try {
|
|
66
|
+
const pemType = cleaned.match(/-----BEGIN\s+([^-]+)-----/)?.[1]?.trim();
|
|
67
|
+
if (pemType?.includes('CERTIFICATE')) {
|
|
68
|
+
const cert = new X509Certificate(pem);
|
|
69
|
+
if (cert.validFrom && cert.validTo) {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const from = Date.parse(cert.validFrom);
|
|
72
|
+
const to = Date.parse(cert.validTo);
|
|
73
|
+
if (now < from || now > to) {
|
|
74
|
+
throw new Error('Certificate is not currently valid');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!cert.subject || !cert.issuer) {
|
|
78
|
+
throw new Error('Certificate missing subject or issuer');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (pemType?.includes('PRIVATE KEY')) {
|
|
82
|
+
createPrivateKey({ key: pem, format: 'pem' });
|
|
83
|
+
}
|
|
84
|
+
else if (pemType?.includes('PUBLIC KEY')) {
|
|
85
|
+
createPublicKey({ key: pem, format: 'pem' });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (validationError) {
|
|
89
|
+
throw new Error(`PEM validation failed: ${validationError instanceof Error ? validationError.message : String(validationError)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw new Error(`Failed to decode base64 content: ${error instanceof Error ? error.message : String(error)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function extractPrivateKeyRaw(pemPrivateKey) {
|
|
99
|
+
if (typeof pemPrivateKey !== 'string') {
|
|
100
|
+
throw new TypeError('Expected a string for PEM private key input');
|
|
101
|
+
}
|
|
102
|
+
const keyBlock = /-----BEGIN (?:EC )?PRIVATE KEY-----[^-]+-----END (?:EC )?PRIVATE KEY-----/s.exec(pemPrivateKey);
|
|
103
|
+
if (!keyBlock) {
|
|
104
|
+
throw new Error('No EC PRIVATE KEY block found in the supplied PEM');
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const privateKey = createPrivateKey(keyBlock[0]);
|
|
108
|
+
const pkcs8Der = privateKey.export({ format: 'der', type: 'pkcs8' });
|
|
109
|
+
if (pkcs8Der.length < 32) {
|
|
110
|
+
throw new Error('Invalid private key: DER data too short');
|
|
111
|
+
}
|
|
112
|
+
const rawPrivateKey = pkcs8Der.subarray(pkcs8Der.length - 32);
|
|
113
|
+
return new Uint8Array(rawPrivateKey);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new Error(`Failed to extract private key: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
3
|
"main.css": "./static/css/main.944b63c3.css",
|
|
4
|
-
"main.js": "./static/js/main.
|
|
4
|
+
"main.js": "./static/js/main.bb47a7dc.js",
|
|
5
5
|
"static/js/453.d855a71b.chunk.js": "./static/js/453.d855a71b.chunk.js",
|
|
6
6
|
"static/media/roboto-latin-700-normal.woff2": "./static/media/roboto-latin-700-normal.c4d6cab43bec89049809.woff2",
|
|
7
7
|
"static/media/roboto-latin-500-normal.woff2": "./static/media/roboto-latin-500-normal.599f66a60bdf974e578e.woff2",
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
"static/media/roboto-greek-ext-300-normal.woff": "./static/media/roboto-greek-ext-300-normal.60729cafbded24073dfb.woff",
|
|
78
78
|
"index.html": "./index.html",
|
|
79
79
|
"main.944b63c3.css.map": "./static/css/main.944b63c3.css.map",
|
|
80
|
-
"main.
|
|
80
|
+
"main.bb47a7dc.js.map": "./static/js/main.bb47a7dc.js.map",
|
|
81
81
|
"453.d855a71b.chunk.js.map": "./static/js/453.d855a71b.chunk.js.map"
|
|
82
82
|
},
|
|
83
83
|
"entrypoints": [
|
|
84
84
|
"static/css/main.944b63c3.css",
|
|
85
|
-
"static/js/main.
|
|
85
|
+
"static/js/main.bb47a7dc.js"
|
|
86
86
|
]
|
|
87
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.bb47a7dc.js"></script><link href="./static/css/main.944b63c3.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|