matterbridge 3.3.1-dev-20251012-b3546f8 → 3.3.1
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 +3 -3
- package/dist/broadcastServer.d.ts +105 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +86 -1
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +717 -0
- package/dist/broadcastServerTypes.d.ts.map +1 -0
- package/dist/broadcastServerTypes.js +24 -0
- package/dist/broadcastServerTypes.js.map +1 -0
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +143 -5
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +50 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +37 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +70 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +46 -2
- package/dist/cliHistory.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +117 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +124 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +60 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +55 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +25 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +234 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +402 -29
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +522 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +469 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +795 -45
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1747 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +65 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +761 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +630 -17
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +36 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1534 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1398 -58
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +345 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +402 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +341 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +209 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +26 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +353 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +325 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +99 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +13 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/hex.d.ts +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/jestHelpers.d.ts +137 -0
- package/dist/utils/jestHelpers.d.ts.map +1 -0
- package/dist/utils/jestHelpers.js +153 -3
- package/dist/utils/jestHelpers.js.map +1 -0
- package/dist/utils/network.d.ts +115 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +108 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +35 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +71 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +54 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Frontend.
|
|
3
|
+
*
|
|
4
|
+
* @file frontend.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2025-01-13
|
|
7
|
+
* @version 1.3.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
1
25
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
2
26
|
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
3
27
|
import os from 'node:os';
|
|
4
28
|
import path from 'node:path';
|
|
5
29
|
import EventEmitter from 'node:events';
|
|
30
|
+
// AnsiLogger module
|
|
6
31
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
|
|
32
|
+
// @matter
|
|
7
33
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
|
|
8
34
|
import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
|
|
9
35
|
import { PowerSource } from '@matter/main/clusters/power-source';
|
|
@@ -33,7 +59,7 @@ export class Frontend extends EventEmitter {
|
|
|
33
59
|
constructor(matterbridge) {
|
|
34
60
|
super();
|
|
35
61
|
this.matterbridge = matterbridge;
|
|
36
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
62
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
37
63
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
38
64
|
this.server = new BroadcastServer('plugins', this.log);
|
|
39
65
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -95,13 +121,42 @@ export class Frontend extends EventEmitter {
|
|
|
95
121
|
async start(port = 8283) {
|
|
96
122
|
this.port = port;
|
|
97
123
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
124
|
+
// Initialize multer with the upload directory
|
|
98
125
|
const multer = await import('multer');
|
|
99
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
126
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
100
127
|
const upload = multer.default({ dest: uploadDir });
|
|
128
|
+
// Create the express app that serves the frontend
|
|
101
129
|
const express = await import('express');
|
|
102
130
|
this.expressApp = express.default();
|
|
131
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
132
|
+
/*
|
|
133
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
134
|
+
for (const method of methods) {
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
139
|
+
try {
|
|
140
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
141
|
+
return original(path, ...rest);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
*/
|
|
149
|
+
// Log all requests to the server for debugging
|
|
150
|
+
/*
|
|
151
|
+
this.expressApp.use((req, res, next) => {
|
|
152
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
153
|
+
next();
|
|
154
|
+
});
|
|
155
|
+
*/
|
|
156
|
+
// Serve static files from 'frontend/build' directory
|
|
103
157
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
104
158
|
if (!hasParameter('ssl')) {
|
|
159
|
+
// Create an HTTP server and attach the express app
|
|
105
160
|
const http = await import('node:http');
|
|
106
161
|
try {
|
|
107
162
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -112,6 +167,7 @@ export class Frontend extends EventEmitter {
|
|
|
112
167
|
this.emit('server_error', error);
|
|
113
168
|
return;
|
|
114
169
|
}
|
|
170
|
+
// Listen on the specified port
|
|
115
171
|
if (hasParameter('ingress')) {
|
|
116
172
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
117
173
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -144,6 +200,7 @@ export class Frontend extends EventEmitter {
|
|
|
144
200
|
});
|
|
145
201
|
}
|
|
146
202
|
else {
|
|
203
|
+
// SSL is enabled, load the certificate and the private key
|
|
147
204
|
let cert;
|
|
148
205
|
let key;
|
|
149
206
|
let ca;
|
|
@@ -153,6 +210,7 @@ export class Frontend extends EventEmitter {
|
|
|
153
210
|
let httpsServerOptions = {};
|
|
154
211
|
const fs = await import('node:fs');
|
|
155
212
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
213
|
+
// Load the p12 certificate and the passphrase
|
|
156
214
|
try {
|
|
157
215
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
158
216
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -164,7 +222,7 @@ export class Frontend extends EventEmitter {
|
|
|
164
222
|
}
|
|
165
223
|
try {
|
|
166
224
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
167
|
-
passphrase = passphrase.trim();
|
|
225
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
168
226
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
169
227
|
}
|
|
170
228
|
catch (error) {
|
|
@@ -175,6 +233,7 @@ export class Frontend extends EventEmitter {
|
|
|
175
233
|
httpsServerOptions = { pfx, passphrase };
|
|
176
234
|
}
|
|
177
235
|
else {
|
|
236
|
+
// Load the SSL certificate, the private key and optionally the CA certificate. If the CA certificate is present, it will be used to create a full chain certificate.
|
|
178
237
|
try {
|
|
179
238
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
180
239
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -204,9 +263,10 @@ export class Frontend extends EventEmitter {
|
|
|
204
263
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
205
264
|
}
|
|
206
265
|
if (hasParameter('mtls')) {
|
|
207
|
-
httpsServerOptions.requestCert = true;
|
|
208
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
266
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
267
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
209
268
|
}
|
|
269
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
210
270
|
const https = await import('node:https');
|
|
211
271
|
try {
|
|
212
272
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -217,6 +277,7 @@ export class Frontend extends EventEmitter {
|
|
|
217
277
|
this.emit('server_error', error);
|
|
218
278
|
return;
|
|
219
279
|
}
|
|
280
|
+
// Listen on the specified port
|
|
220
281
|
if (hasParameter('ingress')) {
|
|
221
282
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
222
283
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -248,16 +309,18 @@ export class Frontend extends EventEmitter {
|
|
|
248
309
|
return;
|
|
249
310
|
});
|
|
250
311
|
}
|
|
312
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
251
313
|
const ws = await import('ws');
|
|
252
314
|
this.log.debug(`Creating WebSocketServer...`);
|
|
253
315
|
this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
254
316
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
255
317
|
const clientIp = request.socket.remoteAddress;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
318
|
+
// Set the global logger callback for the WebSocketServer
|
|
319
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
320
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
321
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
322
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
323
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
261
324
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
262
325
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
263
326
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -279,6 +342,7 @@ export class Frontend extends EventEmitter {
|
|
|
279
342
|
}
|
|
280
343
|
});
|
|
281
344
|
ws.on('error', (error) => {
|
|
345
|
+
// istanbul ignore next
|
|
282
346
|
this.log.error(`WebSocket client error: ${error}`);
|
|
283
347
|
});
|
|
284
348
|
});
|
|
@@ -292,6 +356,7 @@ export class Frontend extends EventEmitter {
|
|
|
292
356
|
this.webSocketServer.on('error', (ws, error) => {
|
|
293
357
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
294
358
|
});
|
|
359
|
+
// Subscribe to cli events
|
|
295
360
|
cliEmitter.removeAllListeners();
|
|
296
361
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
297
362
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -302,6 +367,8 @@ export class Frontend extends EventEmitter {
|
|
|
302
367
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
303
368
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
304
369
|
});
|
|
370
|
+
// Endpoint to validate login code
|
|
371
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
305
372
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
306
373
|
const { password } = req.body;
|
|
307
374
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -320,23 +387,27 @@ export class Frontend extends EventEmitter {
|
|
|
320
387
|
this.log.warn('/api/login error wrong password');
|
|
321
388
|
res.json({ valid: false });
|
|
322
389
|
}
|
|
390
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
323
391
|
}
|
|
324
392
|
catch (error) {
|
|
325
393
|
this.log.error('/api/login error getting password');
|
|
326
394
|
res.json({ valid: false });
|
|
327
395
|
}
|
|
328
396
|
});
|
|
397
|
+
// Endpoint to provide health check for docker
|
|
329
398
|
this.expressApp.get('/health', (req, res) => {
|
|
330
399
|
this.log.debug('Express received /health');
|
|
331
400
|
const healthStatus = {
|
|
332
|
-
status: 'ok',
|
|
333
|
-
uptime: process.uptime(),
|
|
334
|
-
timestamp: new Date().toISOString(),
|
|
401
|
+
status: 'ok', // Indicate service is healthy
|
|
402
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
403
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
335
404
|
};
|
|
336
405
|
res.status(200).json(healthStatus);
|
|
337
406
|
});
|
|
407
|
+
// Endpoint to provide memory usage details
|
|
338
408
|
this.expressApp.get('/memory', async (req, res) => {
|
|
339
409
|
this.log.debug('Express received /memory');
|
|
410
|
+
// Memory usage from process
|
|
340
411
|
const memoryUsageRaw = process.memoryUsage();
|
|
341
412
|
const memoryUsage = {
|
|
342
413
|
rss: formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -345,10 +416,13 @@ export class Frontend extends EventEmitter {
|
|
|
345
416
|
external: formatMemoryUsage(memoryUsageRaw.external),
|
|
346
417
|
arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
347
418
|
};
|
|
419
|
+
// V8 heap statistics
|
|
348
420
|
const { default: v8 } = await import('node:v8');
|
|
349
421
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
350
422
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
423
|
+
// Format heapStats
|
|
351
424
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
|
|
425
|
+
// Format heapSpaces
|
|
352
426
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
353
427
|
...space,
|
|
354
428
|
space_size: formatMemoryUsage(space.space_size),
|
|
@@ -367,18 +441,22 @@ export class Frontend extends EventEmitter {
|
|
|
367
441
|
};
|
|
368
442
|
res.status(200).json(memoryReport);
|
|
369
443
|
});
|
|
444
|
+
// Endpoint to provide settings
|
|
370
445
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
371
446
|
this.log.debug('The frontend sent /api/settings');
|
|
372
447
|
res.json(await this.getApiSettings());
|
|
373
448
|
});
|
|
449
|
+
// Endpoint to provide plugins
|
|
374
450
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
375
451
|
this.log.debug('The frontend sent /api/plugins');
|
|
376
452
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
377
453
|
});
|
|
454
|
+
// Endpoint to provide devices
|
|
378
455
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
379
456
|
this.log.debug('The frontend sent /api/devices');
|
|
380
457
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
381
458
|
});
|
|
459
|
+
// Endpoint to view the matterbridge log
|
|
382
460
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
383
461
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
384
462
|
try {
|
|
@@ -392,6 +470,7 @@ export class Frontend extends EventEmitter {
|
|
|
392
470
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
393
471
|
}
|
|
394
472
|
});
|
|
473
|
+
// Endpoint to view the matter.js log
|
|
395
474
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
396
475
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
397
476
|
try {
|
|
@@ -405,9 +484,11 @@ export class Frontend extends EventEmitter {
|
|
|
405
484
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
406
485
|
}
|
|
407
486
|
});
|
|
487
|
+
// Endpoint to view the diagnostic.log
|
|
408
488
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
409
489
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
410
490
|
const serverNodes = [];
|
|
491
|
+
// istanbul ignore else
|
|
411
492
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
412
493
|
if (this.matterbridge.serverNode)
|
|
413
494
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -418,6 +499,7 @@ export class Frontend extends EventEmitter {
|
|
|
418
499
|
serverNodes.push(plugin.serverNode);
|
|
419
500
|
}
|
|
420
501
|
}
|
|
502
|
+
// istanbul ignore next
|
|
421
503
|
for (const device of this.matterbridge.devices.array()) {
|
|
422
504
|
if (device.serverNode)
|
|
423
505
|
serverNodes.push(device.serverNode);
|
|
@@ -441,7 +523,7 @@ export class Frontend extends EventEmitter {
|
|
|
441
523
|
values: [...serverNodes],
|
|
442
524
|
})));
|
|
443
525
|
delete Logger.destinations.diagnostic;
|
|
444
|
-
await wait(500);
|
|
526
|
+
await wait(500); // Wait for the log to be written
|
|
445
527
|
try {
|
|
446
528
|
const fs = await import('node:fs');
|
|
447
529
|
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
|
|
@@ -449,10 +531,13 @@ export class Frontend extends EventEmitter {
|
|
|
449
531
|
res.send(data.slice(29));
|
|
450
532
|
}
|
|
451
533
|
catch (error) {
|
|
534
|
+
// istanbul ignore next
|
|
452
535
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
536
|
+
// istanbul ignore next
|
|
453
537
|
res.status(500).send('Error reading diagnostic log file.');
|
|
454
538
|
}
|
|
455
539
|
});
|
|
540
|
+
// Endpoint to view the history.html
|
|
456
541
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
457
542
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
458
543
|
try {
|
|
@@ -466,6 +551,7 @@ export class Frontend extends EventEmitter {
|
|
|
466
551
|
res.status(500).send('Error reading history log file. Please create the history log before loading it.');
|
|
467
552
|
}
|
|
468
553
|
});
|
|
554
|
+
// Endpoint to view the shelly log
|
|
469
555
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
470
556
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
471
557
|
try {
|
|
@@ -479,6 +565,7 @@ export class Frontend extends EventEmitter {
|
|
|
479
565
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
480
566
|
}
|
|
481
567
|
});
|
|
568
|
+
// Endpoint to download the matterbridge log
|
|
482
569
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
483
570
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
484
571
|
const fs = await import('node:fs');
|
|
@@ -493,12 +580,14 @@ export class Frontend extends EventEmitter {
|
|
|
493
580
|
}
|
|
494
581
|
res.type('text/plain');
|
|
495
582
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
583
|
+
/* istanbul ignore if */
|
|
496
584
|
if (error) {
|
|
497
585
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
498
586
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
499
587
|
}
|
|
500
588
|
});
|
|
501
589
|
});
|
|
590
|
+
// Endpoint to download the matter log
|
|
502
591
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
503
592
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
504
593
|
const fs = await import('node:fs');
|
|
@@ -513,12 +602,14 @@ export class Frontend extends EventEmitter {
|
|
|
513
602
|
}
|
|
514
603
|
res.type('text/plain');
|
|
515
604
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
605
|
+
/* istanbul ignore if */
|
|
516
606
|
if (error) {
|
|
517
607
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
518
608
|
res.status(500).send('Error downloading the matter log file');
|
|
519
609
|
}
|
|
520
610
|
});
|
|
521
611
|
});
|
|
612
|
+
// Endpoint to download the shelly log
|
|
522
613
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
523
614
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
524
615
|
const fs = await import('node:fs');
|
|
@@ -533,75 +624,91 @@ export class Frontend extends EventEmitter {
|
|
|
533
624
|
}
|
|
534
625
|
res.type('text/plain');
|
|
535
626
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
627
|
+
/* istanbul ignore if */
|
|
536
628
|
if (error) {
|
|
537
629
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
538
630
|
res.status(500).send('Error downloading Shelly system log file');
|
|
539
631
|
}
|
|
540
632
|
});
|
|
541
633
|
});
|
|
634
|
+
// Endpoint to download the matterbridge storage directory
|
|
542
635
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
543
636
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
544
637
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
545
638
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
639
|
+
/* istanbul ignore if */
|
|
546
640
|
if (error) {
|
|
547
641
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
548
642
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
549
643
|
}
|
|
550
644
|
});
|
|
551
645
|
});
|
|
646
|
+
// Endpoint to download the matter storage file
|
|
552
647
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
553
648
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
554
649
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
555
650
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
651
|
+
/* istanbul ignore if */
|
|
556
652
|
if (error) {
|
|
557
653
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
558
654
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
559
655
|
}
|
|
560
656
|
});
|
|
561
657
|
});
|
|
658
|
+
// Endpoint to download the matterbridge plugin directory
|
|
562
659
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
563
660
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
564
661
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
565
662
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
663
|
+
/* istanbul ignore if */
|
|
566
664
|
if (error) {
|
|
567
665
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
568
666
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
569
667
|
}
|
|
570
668
|
});
|
|
571
669
|
});
|
|
670
|
+
// Endpoint to download the matterbridge plugin config files
|
|
572
671
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
573
672
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
574
673
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
575
674
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
675
|
+
/* istanbul ignore if */
|
|
576
676
|
if (error) {
|
|
577
677
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
578
678
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
579
679
|
}
|
|
580
680
|
});
|
|
581
681
|
});
|
|
682
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
582
683
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
583
684
|
this.log.debug('The frontend sent /api/download-backup');
|
|
584
685
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
686
|
+
/* istanbul ignore if */
|
|
585
687
|
if (error) {
|
|
586
688
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
587
689
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
588
690
|
}
|
|
589
691
|
});
|
|
590
692
|
});
|
|
693
|
+
// Endpoint to upload a package
|
|
591
694
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
592
695
|
const { filename } = req.body;
|
|
593
696
|
const file = req.file;
|
|
697
|
+
/* istanbul ignore if */
|
|
594
698
|
if (!file || !filename) {
|
|
595
699
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
596
700
|
res.status(400).send('Invalid request: file and filename are required');
|
|
597
701
|
return;
|
|
598
702
|
}
|
|
599
703
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
704
|
+
// Define the path where the plugin file will be saved
|
|
600
705
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
601
706
|
try {
|
|
707
|
+
// Move the uploaded file to the specified path
|
|
602
708
|
const fs = await import('node:fs');
|
|
603
709
|
await fs.promises.rename(file.path, filePath);
|
|
604
710
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
711
|
+
// Install the plugin package
|
|
605
712
|
if (filename.endsWith('.tgz')) {
|
|
606
713
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
607
714
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -621,6 +728,7 @@ export class Frontend extends EventEmitter {
|
|
|
621
728
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
622
729
|
}
|
|
623
730
|
});
|
|
731
|
+
// Fallback for routing (must be the last route)
|
|
624
732
|
this.expressApp.use((req, res) => {
|
|
625
733
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
626
734
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -630,13 +738,16 @@ export class Frontend extends EventEmitter {
|
|
|
630
738
|
async stop() {
|
|
631
739
|
this.log.debug('Stopping the frontend...');
|
|
632
740
|
const ws = await import('ws');
|
|
741
|
+
// Remove listeners from the express app
|
|
633
742
|
if (this.expressApp) {
|
|
634
743
|
this.expressApp.removeAllListeners();
|
|
635
744
|
this.expressApp = undefined;
|
|
636
745
|
this.log.debug('Frontend app closed successfully');
|
|
637
746
|
}
|
|
747
|
+
// Close the WebSocket server
|
|
638
748
|
if (this.webSocketServer) {
|
|
639
749
|
this.log.debug('Closing WebSocket server...');
|
|
750
|
+
// Close all active connections
|
|
640
751
|
this.webSocketServer.clients.forEach((client) => {
|
|
641
752
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
642
753
|
client.close();
|
|
@@ -645,6 +756,7 @@ export class Frontend extends EventEmitter {
|
|
|
645
756
|
await withTimeout(new Promise((resolve) => {
|
|
646
757
|
this.webSocketServer?.close((error) => {
|
|
647
758
|
if (error) {
|
|
759
|
+
// istanbul ignore next
|
|
648
760
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
649
761
|
}
|
|
650
762
|
else {
|
|
@@ -657,8 +769,27 @@ export class Frontend extends EventEmitter {
|
|
|
657
769
|
this.webSocketServer.removeAllListeners();
|
|
658
770
|
this.webSocketServer = undefined;
|
|
659
771
|
}
|
|
772
|
+
// Close the http server
|
|
660
773
|
if (this.httpServer) {
|
|
661
774
|
this.log.debug('Closing http server...');
|
|
775
|
+
/*
|
|
776
|
+
await withTimeout(
|
|
777
|
+
new Promise<void>((resolve) => {
|
|
778
|
+
this.httpServer?.close((error) => {
|
|
779
|
+
if (error) {
|
|
780
|
+
// istanbul ignore next
|
|
781
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
782
|
+
} else {
|
|
783
|
+
this.log.debug('Http server closed successfully');
|
|
784
|
+
this.emit('server_stopped');
|
|
785
|
+
}
|
|
786
|
+
resolve();
|
|
787
|
+
});
|
|
788
|
+
}),
|
|
789
|
+
5000,
|
|
790
|
+
false,
|
|
791
|
+
);
|
|
792
|
+
*/
|
|
662
793
|
this.httpServer.close();
|
|
663
794
|
this.log.debug('Http server closed successfully');
|
|
664
795
|
this.listening = false;
|
|
@@ -667,8 +798,27 @@ export class Frontend extends EventEmitter {
|
|
|
667
798
|
this.httpServer = undefined;
|
|
668
799
|
this.log.debug('Frontend http server closed successfully');
|
|
669
800
|
}
|
|
801
|
+
// Close the https server
|
|
670
802
|
if (this.httpsServer) {
|
|
671
803
|
this.log.debug('Closing https server...');
|
|
804
|
+
/*
|
|
805
|
+
await withTimeout(
|
|
806
|
+
new Promise<void>((resolve) => {
|
|
807
|
+
this.httpsServer?.close((error) => {
|
|
808
|
+
if (error) {
|
|
809
|
+
// istanbul ignore next
|
|
810
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
811
|
+
} else {
|
|
812
|
+
this.log.debug('Https server closed successfully');
|
|
813
|
+
this.emit('server_stopped');
|
|
814
|
+
}
|
|
815
|
+
resolve();
|
|
816
|
+
});
|
|
817
|
+
}),
|
|
818
|
+
5000,
|
|
819
|
+
false,
|
|
820
|
+
);
|
|
821
|
+
*/
|
|
672
822
|
this.httpsServer.close();
|
|
673
823
|
this.log.debug('Https server closed successfully');
|
|
674
824
|
this.listening = false;
|
|
@@ -679,7 +829,13 @@ export class Frontend extends EventEmitter {
|
|
|
679
829
|
}
|
|
680
830
|
this.log.debug('Frontend stopped successfully');
|
|
681
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Retrieves the api settings data.
|
|
834
|
+
*
|
|
835
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
836
|
+
*/
|
|
682
837
|
async getApiSettings() {
|
|
838
|
+
// Update the variable system information properties
|
|
683
839
|
this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
684
840
|
this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
685
841
|
this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
@@ -689,6 +845,7 @@ export class Frontend extends EventEmitter {
|
|
|
689
845
|
this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
690
846
|
this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
691
847
|
this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
848
|
+
// Create the matterbridge information
|
|
692
849
|
const info = {
|
|
693
850
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
694
851
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -724,9 +881,15 @@ export class Frontend extends EventEmitter {
|
|
|
724
881
|
};
|
|
725
882
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
726
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Retrieves the reachable attribute.
|
|
886
|
+
*
|
|
887
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
888
|
+
* @returns {boolean} The reachable attribute.
|
|
889
|
+
*/
|
|
727
890
|
getReachability(device) {
|
|
728
891
|
if (this.matterbridge.hasCleanupStarted)
|
|
729
|
-
return false;
|
|
892
|
+
return false; // Skip if cleanup has started
|
|
730
893
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
731
894
|
return false;
|
|
732
895
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -737,9 +900,15 @@ export class Frontend extends EventEmitter {
|
|
|
737
900
|
return true;
|
|
738
901
|
return false;
|
|
739
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* Retrieves the power source attribute.
|
|
905
|
+
*
|
|
906
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
907
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
908
|
+
*/
|
|
740
909
|
getPowerSource(endpoint) {
|
|
741
910
|
if (this.matterbridge.hasCleanupStarted)
|
|
742
|
-
return;
|
|
911
|
+
return; // Skip if cleanup has started
|
|
743
912
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
744
913
|
return undefined;
|
|
745
914
|
const powerSource = (device) => {
|
|
@@ -754,16 +923,25 @@ export class Frontend extends EventEmitter {
|
|
|
754
923
|
}
|
|
755
924
|
return;
|
|
756
925
|
};
|
|
926
|
+
// Root endpoint
|
|
757
927
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
758
928
|
return powerSource(endpoint);
|
|
929
|
+
// Child endpoints
|
|
759
930
|
for (const child of endpoint.getChildEndpoints()) {
|
|
760
931
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
761
932
|
return powerSource(child);
|
|
762
933
|
}
|
|
763
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Retrieves the cluster text description from a given device.
|
|
937
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
938
|
+
*
|
|
939
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
940
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
941
|
+
*/
|
|
764
942
|
getClusterTextFromDevice(device) {
|
|
765
943
|
if (this.matterbridge.hasCleanupStarted)
|
|
766
|
-
return '';
|
|
944
|
+
return ''; // Skip if cleanup has started
|
|
767
945
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
768
946
|
return '';
|
|
769
947
|
const getUserLabel = (device) => {
|
|
@@ -773,6 +951,7 @@ export class Frontend extends EventEmitter {
|
|
|
773
951
|
if (composed)
|
|
774
952
|
return 'Composed: ' + composed.value;
|
|
775
953
|
}
|
|
954
|
+
// istanbul ignore next cause is not reachable
|
|
776
955
|
return '';
|
|
777
956
|
};
|
|
778
957
|
const getFixedLabel = (device) => {
|
|
@@ -782,11 +961,13 @@ export class Frontend extends EventEmitter {
|
|
|
782
961
|
if (composed)
|
|
783
962
|
return 'Composed: ' + composed.value;
|
|
784
963
|
}
|
|
964
|
+
// istanbul ignore next cause is not reacheable
|
|
785
965
|
return '';
|
|
786
966
|
};
|
|
787
967
|
let attributes = '';
|
|
788
968
|
let supportedModes = [];
|
|
789
969
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
970
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
790
971
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
791
972
|
return;
|
|
792
973
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -876,11 +1057,17 @@ export class Frontend extends EventEmitter {
|
|
|
876
1057
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
877
1058
|
attributes += `${getUserLabel(device)} `;
|
|
878
1059
|
});
|
|
1060
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
879
1061
|
return attributes.trimStart().trimEnd();
|
|
880
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1065
|
+
*
|
|
1066
|
+
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1067
|
+
*/
|
|
881
1068
|
getPlugins() {
|
|
882
1069
|
if (this.matterbridge.hasCleanupStarted)
|
|
883
|
-
return [];
|
|
1070
|
+
return []; // Skip if cleanup has started
|
|
884
1071
|
const plugins = [];
|
|
885
1072
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
886
1073
|
plugins.push({
|
|
@@ -908,18 +1095,27 @@ export class Frontend extends EventEmitter {
|
|
|
908
1095
|
schemaJson: plugin.schemaJson,
|
|
909
1096
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
910
1097
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1098
|
+
// Childbridge mode specific data
|
|
911
1099
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
912
1100
|
});
|
|
913
1101
|
}
|
|
914
1102
|
return plugins;
|
|
915
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Retrieves the devices from Matterbridge.
|
|
1106
|
+
*
|
|
1107
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1108
|
+
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1109
|
+
*/
|
|
916
1110
|
getDevices(pluginName) {
|
|
917
1111
|
if (this.matterbridge.hasCleanupStarted)
|
|
918
|
-
return [];
|
|
1112
|
+
return []; // Skip if cleanup has started
|
|
919
1113
|
const devices = [];
|
|
920
1114
|
for (const device of this.matterbridge.devices.array()) {
|
|
1115
|
+
// Filter by pluginName if provided
|
|
921
1116
|
if (pluginName && pluginName !== device.plugin)
|
|
922
1117
|
continue;
|
|
1118
|
+
// Check if the device has the required properties
|
|
923
1119
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
924
1120
|
continue;
|
|
925
1121
|
devices.push({
|
|
@@ -939,24 +1135,39 @@ export class Frontend extends EventEmitter {
|
|
|
939
1135
|
}
|
|
940
1136
|
return devices;
|
|
941
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1140
|
+
*
|
|
1141
|
+
* Response for /api/clusters
|
|
1142
|
+
*
|
|
1143
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1144
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1145
|
+
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1146
|
+
*/
|
|
942
1147
|
getClusters(pluginName, endpointNumber) {
|
|
943
1148
|
if (this.matterbridge.hasCleanupStarted)
|
|
944
|
-
return;
|
|
1149
|
+
return; // Skip if cleanup has started
|
|
945
1150
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
946
1151
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
947
1152
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
948
1153
|
return;
|
|
949
1154
|
}
|
|
1155
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1156
|
+
// Get the device types from the main endpoint
|
|
950
1157
|
const deviceTypes = [];
|
|
951
1158
|
const clusters = [];
|
|
952
1159
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
953
1160
|
deviceTypes.push(d.deviceType);
|
|
954
1161
|
});
|
|
1162
|
+
// Get the clusters from the main endpoint
|
|
955
1163
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
956
1164
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
957
1165
|
return;
|
|
958
1166
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
959
1167
|
return;
|
|
1168
|
+
// console.log(
|
|
1169
|
+
// `${idn}${endpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
|
|
1170
|
+
// );
|
|
960
1171
|
clusters.push({
|
|
961
1172
|
endpoint: endpoint.number.toString(),
|
|
962
1173
|
number: endpoint.number,
|
|
@@ -970,12 +1181,19 @@ export class Frontend extends EventEmitter {
|
|
|
970
1181
|
attributeLocalValue: attributeValue,
|
|
971
1182
|
});
|
|
972
1183
|
});
|
|
1184
|
+
// Get the child endpoints
|
|
973
1185
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1186
|
+
// if (childEndpoints.length === 0) {
|
|
1187
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1188
|
+
// }
|
|
974
1189
|
childEndpoints.forEach((childEndpoint) => {
|
|
1190
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
975
1191
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
976
1192
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
977
1193
|
return;
|
|
978
1194
|
}
|
|
1195
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1196
|
+
// Get the device types of the child endpoint
|
|
979
1197
|
const deviceTypes = [];
|
|
980
1198
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
981
1199
|
deviceTypes.push(d.deviceType);
|
|
@@ -985,6 +1203,9 @@ export class Frontend extends EventEmitter {
|
|
|
985
1203
|
return;
|
|
986
1204
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
987
1205
|
return;
|
|
1206
|
+
// console.log(
|
|
1207
|
+
// `${idn}${childEndpoint.deviceName}${rs}${nf} => Cluster: ${CYAN}${clusterName} (0x${clusterId.toString(16).padStart(2, '0')})${nf} Attribute: ${CYAN}${attributeName} (0x${attributeId.toString(16).padStart(2, '0')})${nf} Value: ${YELLOW}${typeof attributeValue === 'object' ? stringify(attributeValue as object) : attributeValue}${nf}`,
|
|
1208
|
+
// );
|
|
988
1209
|
clusters.push({
|
|
989
1210
|
endpoint: childEndpoint.number.toString(),
|
|
990
1211
|
number: childEndpoint.number,
|
|
@@ -1001,6 +1222,13 @@ export class Frontend extends EventEmitter {
|
|
|
1001
1222
|
});
|
|
1002
1223
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
|
|
1003
1224
|
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1227
|
+
*
|
|
1228
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1229
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1230
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1231
|
+
*/
|
|
1004
1232
|
async wsMessageHandler(client, message) {
|
|
1005
1233
|
let data;
|
|
1006
1234
|
const sendResponse = (data) => {
|
|
@@ -1020,7 +1248,7 @@ export class Frontend extends EventEmitter {
|
|
|
1020
1248
|
};
|
|
1021
1249
|
try {
|
|
1022
1250
|
data = JSON.parse(message.toString());
|
|
1023
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1251
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
1024
1252
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1025
1253
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1026
1254
|
return;
|
|
@@ -1094,6 +1322,7 @@ export class Frontend extends EventEmitter {
|
|
|
1094
1322
|
return;
|
|
1095
1323
|
})
|
|
1096
1324
|
.catch((_error) => {
|
|
1325
|
+
//
|
|
1097
1326
|
});
|
|
1098
1327
|
}
|
|
1099
1328
|
else {
|
|
@@ -1141,6 +1370,7 @@ export class Frontend extends EventEmitter {
|
|
|
1141
1370
|
return;
|
|
1142
1371
|
})
|
|
1143
1372
|
.catch((_error) => {
|
|
1373
|
+
//
|
|
1144
1374
|
});
|
|
1145
1375
|
}
|
|
1146
1376
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1166,6 +1396,7 @@ export class Frontend extends EventEmitter {
|
|
|
1166
1396
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1167
1397
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1168
1398
|
if (plugin.serverNode) {
|
|
1399
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1169
1400
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1170
1401
|
plugin.serverNode = undefined;
|
|
1171
1402
|
}
|
|
@@ -1175,18 +1406,20 @@ export class Frontend extends EventEmitter {
|
|
|
1175
1406
|
this.matterbridge.devices.remove(device);
|
|
1176
1407
|
}
|
|
1177
1408
|
}
|
|
1409
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1178
1410
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1179
1411
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1180
1412
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1181
|
-
plugin.restartRequired = false;
|
|
1413
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1182
1414
|
let needRestart = 0;
|
|
1183
1415
|
for (const plugin of this.matterbridge.plugins) {
|
|
1184
1416
|
if (plugin.restartRequired)
|
|
1185
1417
|
needRestart++;
|
|
1186
1418
|
}
|
|
1187
1419
|
if (needRestart === 0) {
|
|
1188
|
-
this.wssSendRestartNotRequired(true);
|
|
1420
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1189
1421
|
}
|
|
1422
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1190
1423
|
if (plugin.serverNode)
|
|
1191
1424
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1192
1425
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1447,22 +1680,22 @@ export class Frontend extends EventEmitter {
|
|
|
1447
1680
|
if (isValidString(data.params.value, 4)) {
|
|
1448
1681
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1449
1682
|
if (data.params.value === 'Debug') {
|
|
1450
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1683
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1451
1684
|
}
|
|
1452
1685
|
else if (data.params.value === 'Info') {
|
|
1453
|
-
await this.matterbridge.setLogLevel("info");
|
|
1686
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1454
1687
|
}
|
|
1455
1688
|
else if (data.params.value === 'Notice') {
|
|
1456
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1689
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1457
1690
|
}
|
|
1458
1691
|
else if (data.params.value === 'Warn') {
|
|
1459
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1692
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1460
1693
|
}
|
|
1461
1694
|
else if (data.params.value === 'Error') {
|
|
1462
|
-
await this.matterbridge.setLogLevel("error");
|
|
1695
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1463
1696
|
}
|
|
1464
1697
|
else if (data.params.value === 'Fatal') {
|
|
1465
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1698
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1466
1699
|
}
|
|
1467
1700
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1468
1701
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1473,6 +1706,7 @@ export class Frontend extends EventEmitter {
|
|
|
1473
1706
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1474
1707
|
this.matterbridge.fileLogger = data.params.value;
|
|
1475
1708
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1709
|
+
// Create the file logger for matterbridge
|
|
1476
1710
|
if (data.params.value)
|
|
1477
1711
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1478
1712
|
else
|
|
@@ -1550,6 +1784,7 @@ export class Frontend extends EventEmitter {
|
|
|
1550
1784
|
}
|
|
1551
1785
|
break;
|
|
1552
1786
|
case 'setmatterport':
|
|
1787
|
+
// eslint-disable-next-line no-case-declarations
|
|
1553
1788
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1554
1789
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1555
1790
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1569,6 +1804,7 @@ export class Frontend extends EventEmitter {
|
|
|
1569
1804
|
}
|
|
1570
1805
|
break;
|
|
1571
1806
|
case 'setmatterdiscriminator':
|
|
1807
|
+
// eslint-disable-next-line no-case-declarations
|
|
1572
1808
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1573
1809
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1574
1810
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1588,6 +1824,7 @@ export class Frontend extends EventEmitter {
|
|
|
1588
1824
|
}
|
|
1589
1825
|
break;
|
|
1590
1826
|
case 'setmatterpasscode':
|
|
1827
|
+
// eslint-disable-next-line no-case-declarations
|
|
1591
1828
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1592
1829
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1593
1830
|
this.matterbridge.passcode = passcode;
|
|
@@ -1633,15 +1870,19 @@ export class Frontend extends EventEmitter {
|
|
|
1633
1870
|
return;
|
|
1634
1871
|
}
|
|
1635
1872
|
const config = plugin.configJson;
|
|
1873
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1636
1874
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1875
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1637
1876
|
if (select === 'serial')
|
|
1638
1877
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1639
1878
|
if (select === 'name')
|
|
1640
1879
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1641
1880
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1881
|
+
// Remove postfix from the serial if it exists
|
|
1642
1882
|
if (config.postfix) {
|
|
1643
1883
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1644
1884
|
}
|
|
1885
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1645
1886
|
if (isValidArray(config.whiteList, 1)) {
|
|
1646
1887
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1647
1888
|
config.whiteList.push(data.params.serial);
|
|
@@ -1650,6 +1891,7 @@ export class Frontend extends EventEmitter {
|
|
|
1650
1891
|
config.whiteList.push(data.params.name);
|
|
1651
1892
|
}
|
|
1652
1893
|
}
|
|
1894
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1653
1895
|
if (isValidArray(config.blackList, 1)) {
|
|
1654
1896
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1655
1897
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1677,7 +1919,9 @@ export class Frontend extends EventEmitter {
|
|
|
1677
1919
|
return;
|
|
1678
1920
|
}
|
|
1679
1921
|
const config = plugin.configJson;
|
|
1922
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1680
1923
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1924
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1681
1925
|
if (select === 'serial')
|
|
1682
1926
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1683
1927
|
if (select === 'name')
|
|
@@ -1686,6 +1930,7 @@ export class Frontend extends EventEmitter {
|
|
|
1686
1930
|
if (config.postfix) {
|
|
1687
1931
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1688
1932
|
}
|
|
1933
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1689
1934
|
if (isValidArray(config.whiteList, 1)) {
|
|
1690
1935
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1691
1936
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1694,6 +1939,7 @@ export class Frontend extends EventEmitter {
|
|
|
1694
1939
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1695
1940
|
}
|
|
1696
1941
|
}
|
|
1942
|
+
// Add the serial to the blackList
|
|
1697
1943
|
if (isValidArray(config.blackList)) {
|
|
1698
1944
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1699
1945
|
config.blackList.push(data.params.serial);
|
|
@@ -1716,6 +1962,7 @@ export class Frontend extends EventEmitter {
|
|
|
1716
1962
|
}
|
|
1717
1963
|
}
|
|
1718
1964
|
else {
|
|
1965
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1719
1966
|
const localData = data;
|
|
1720
1967
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1721
1968
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1725,23 +1972,46 @@ export class Frontend extends EventEmitter {
|
|
|
1725
1972
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1726
1973
|
}
|
|
1727
1974
|
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1977
|
+
*
|
|
1978
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1979
|
+
* @param {string} time - The time string of the message
|
|
1980
|
+
* @param {string} name - The logger name of the message
|
|
1981
|
+
* @param {string} message - The content of the message.
|
|
1982
|
+
*
|
|
1983
|
+
* @remarks
|
|
1984
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1985
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1986
|
+
* The function sends the message to all connected clients.
|
|
1987
|
+
*/
|
|
1728
1988
|
wssSendLogMessage(level, time, name, message) {
|
|
1729
1989
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1730
1990
|
return;
|
|
1731
1991
|
if (!level || !time || !name || !message)
|
|
1732
1992
|
return;
|
|
1993
|
+
// Remove ANSI escape codes from the message
|
|
1994
|
+
// eslint-disable-next-line no-control-regex
|
|
1733
1995
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1996
|
+
// Remove leading asterisks from the message
|
|
1734
1997
|
message = message.replace(/^\*+/, '');
|
|
1998
|
+
// Replace all occurrences of \t and \n
|
|
1735
1999
|
message = message.replace(/[\t\n]/g, '');
|
|
2000
|
+
// Remove non-printable characters
|
|
2001
|
+
// eslint-disable-next-line no-control-regex
|
|
1736
2002
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2003
|
+
// Replace all occurrences of \" with "
|
|
1737
2004
|
message = message.replace(/\\"/g, '"');
|
|
2005
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1738
2006
|
const maxContinuousLength = 100;
|
|
1739
2007
|
const keepStartLength = 20;
|
|
1740
2008
|
const keepEndLength = 20;
|
|
2009
|
+
// Split the message into words
|
|
1741
2010
|
if (level !== 'spawn') {
|
|
1742
2011
|
message = message
|
|
1743
2012
|
.split(' ')
|
|
1744
2013
|
.map((word) => {
|
|
2014
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1745
2015
|
if (word.length > maxContinuousLength) {
|
|
1746
2016
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1747
2017
|
}
|
|
@@ -1749,14 +2019,34 @@ export class Frontend extends EventEmitter {
|
|
|
1749
2019
|
})
|
|
1750
2020
|
.join(' ');
|
|
1751
2021
|
}
|
|
2022
|
+
// Send the message to all connected clients
|
|
1752
2023
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1753
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2027
|
+
*
|
|
2028
|
+
* @param {string} changed - The changed value.
|
|
2029
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2030
|
+
* possible values for changed:
|
|
2031
|
+
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2032
|
+
* - 'plugins'
|
|
2033
|
+
* - 'devices'
|
|
2034
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2035
|
+
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2036
|
+
*/
|
|
1754
2037
|
wssSendRefreshRequired(changed, params) {
|
|
1755
2038
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1756
2039
|
return;
|
|
1757
2040
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2041
|
+
// Send the message to all connected clients
|
|
1758
2042
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1759
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2046
|
+
*
|
|
2047
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2048
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2049
|
+
*/
|
|
1760
2050
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1761
2051
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1762
2052
|
return;
|
|
@@ -1765,8 +2055,14 @@ export class Frontend extends EventEmitter {
|
|
|
1765
2055
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
1766
2056
|
if (snackbar === true)
|
|
1767
2057
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2058
|
+
// Send the message to all connected clients
|
|
1768
2059
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1769
2060
|
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2063
|
+
*
|
|
2064
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2065
|
+
*/
|
|
1770
2066
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1771
2067
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1772
2068
|
return;
|
|
@@ -1774,57 +2070,133 @@ export class Frontend extends EventEmitter {
|
|
|
1774
2070
|
this.matterbridge.restartRequired = false;
|
|
1775
2071
|
if (snackbar === true)
|
|
1776
2072
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2073
|
+
// Send the message to all connected clients
|
|
1777
2074
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1778
2075
|
}
|
|
2076
|
+
/**
|
|
2077
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2078
|
+
*
|
|
2079
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2080
|
+
*/
|
|
1779
2081
|
wssSendUpdateRequired(devVersion = false) {
|
|
1780
2082
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1781
2083
|
return;
|
|
1782
2084
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1783
2085
|
this.matterbridge.updateRequired = true;
|
|
2086
|
+
// Send the message to all connected clients
|
|
1784
2087
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1785
2088
|
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Sends a cpu update message to all connected clients.
|
|
2091
|
+
*
|
|
2092
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2093
|
+
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2094
|
+
*/
|
|
1786
2095
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
1787
2096
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1788
2097
|
return;
|
|
1789
2098
|
if (hasParameter('debug'))
|
|
1790
2099
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2100
|
+
// Send the message to all connected clients
|
|
1791
2101
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 } });
|
|
1792
2102
|
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Sends a memory update message to all connected clients.
|
|
2105
|
+
*
|
|
2106
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2107
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2108
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2109
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2110
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2111
|
+
* @param {string} external - The external memory in bytes.
|
|
2112
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2113
|
+
*/
|
|
1793
2114
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1794
2115
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1795
2116
|
return;
|
|
1796
2117
|
if (hasParameter('debug'))
|
|
1797
2118
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2119
|
+
// Send the message to all connected clients
|
|
1798
2120
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1799
2121
|
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Sends an uptime update message to all connected clients.
|
|
2124
|
+
*
|
|
2125
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2126
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2127
|
+
*/
|
|
1800
2128
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1801
2129
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1802
2130
|
return;
|
|
1803
2131
|
if (hasParameter('debug'))
|
|
1804
2132
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2133
|
+
// Send the message to all connected clients
|
|
1805
2134
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1806
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Sends an open snackbar message to all connected clients.
|
|
2138
|
+
*
|
|
2139
|
+
* @param {string} message - The message to send.
|
|
2140
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2141
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2142
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2143
|
+
*
|
|
2144
|
+
* @remarks
|
|
2145
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2146
|
+
*/
|
|
1807
2147
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1808
2148
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1809
2149
|
return;
|
|
1810
2150
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2151
|
+
// Send the message to all connected clients
|
|
1811
2152
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1812
2153
|
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Sends a close snackbar message to all connected clients.
|
|
2156
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2157
|
+
*
|
|
2158
|
+
* @param {string} message - The message to send.
|
|
2159
|
+
*/
|
|
1813
2160
|
wssSendCloseSnackbarMessage(message) {
|
|
1814
2161
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1815
2162
|
return;
|
|
1816
2163
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2164
|
+
// Send the message to all connected clients
|
|
1817
2165
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1818
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2169
|
+
*
|
|
2170
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2171
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2172
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2173
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2174
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2175
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2176
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2177
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2178
|
+
*
|
|
2179
|
+
* @remarks
|
|
2180
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2181
|
+
* with the updated attribute information.
|
|
2182
|
+
*/
|
|
1819
2183
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1820
2184
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1821
2185
|
return;
|
|
1822
2186
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2187
|
+
// Send the message to all connected clients
|
|
1823
2188
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1824
2189
|
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Sends a message to all connected clients.
|
|
2192
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2193
|
+
*
|
|
2194
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2195
|
+
*/
|
|
1825
2196
|
wssBroadcastMessage(msg) {
|
|
1826
2197
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1827
2198
|
return;
|
|
2199
|
+
// Send the message to all connected clients
|
|
1828
2200
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1829
2201
|
if (msg.method !== 'log')
|
|
1830
2202
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -1835,3 +2207,4 @@ export class Frontend extends EventEmitter {
|
|
|
1835
2207
|
});
|
|
1836
2208
|
}
|
|
1837
2209
|
}
|
|
2210
|
+
//# sourceMappingURL=frontend.js.map
|