matterbridge 3.3.3-dev-20251017-2e6040a → 3.3.3
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 +1 -1
- 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 +719 -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 +135 -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 +74 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +44 -0
- 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 +235 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +415 -29
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +529 -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 +2399 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +71 -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 +1545 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1412 -58
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +560 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +368 -10
- 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('frontend', this.log);
|
|
39
65
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -93,13 +119,42 @@ export class Frontend extends EventEmitter {
|
|
|
93
119
|
async start(port = 8283) {
|
|
94
120
|
this.port = port;
|
|
95
121
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
122
|
+
// Initialize multer with the upload directory
|
|
96
123
|
const multer = await import('multer');
|
|
97
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
124
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
98
125
|
const upload = multer.default({ dest: uploadDir });
|
|
126
|
+
// Create the express app that serves the frontend
|
|
99
127
|
const express = await import('express');
|
|
100
128
|
this.expressApp = express.default();
|
|
129
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
130
|
+
/*
|
|
131
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
132
|
+
for (const method of methods) {
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
137
|
+
try {
|
|
138
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
139
|
+
return original(path, ...rest);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
*/
|
|
147
|
+
// Log all requests to the server for debugging
|
|
148
|
+
/*
|
|
149
|
+
this.expressApp.use((req, res, next) => {
|
|
150
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
151
|
+
next();
|
|
152
|
+
});
|
|
153
|
+
*/
|
|
154
|
+
// Serve static files from 'frontend/build' directory
|
|
101
155
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
102
156
|
if (!hasParameter('ssl')) {
|
|
157
|
+
// Create an HTTP server and attach the express app
|
|
103
158
|
const http = await import('node:http');
|
|
104
159
|
try {
|
|
105
160
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -110,6 +165,7 @@ export class Frontend extends EventEmitter {
|
|
|
110
165
|
this.emit('server_error', error);
|
|
111
166
|
return;
|
|
112
167
|
}
|
|
168
|
+
// Listen on the specified port
|
|
113
169
|
if (hasParameter('ingress')) {
|
|
114
170
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
115
171
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -142,6 +198,7 @@ export class Frontend extends EventEmitter {
|
|
|
142
198
|
});
|
|
143
199
|
}
|
|
144
200
|
else {
|
|
201
|
+
// SSL is enabled, load the certificate and the private key
|
|
145
202
|
let cert;
|
|
146
203
|
let key;
|
|
147
204
|
let ca;
|
|
@@ -151,6 +208,7 @@ export class Frontend extends EventEmitter {
|
|
|
151
208
|
let httpsServerOptions = {};
|
|
152
209
|
const fs = await import('node:fs');
|
|
153
210
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
211
|
+
// Load the p12 certificate and the passphrase
|
|
154
212
|
try {
|
|
155
213
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
156
214
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -162,7 +220,7 @@ export class Frontend extends EventEmitter {
|
|
|
162
220
|
}
|
|
163
221
|
try {
|
|
164
222
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
165
|
-
passphrase = passphrase.trim();
|
|
223
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
166
224
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
167
225
|
}
|
|
168
226
|
catch (error) {
|
|
@@ -173,6 +231,7 @@ export class Frontend extends EventEmitter {
|
|
|
173
231
|
httpsServerOptions = { pfx, passphrase };
|
|
174
232
|
}
|
|
175
233
|
else {
|
|
234
|
+
// 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.
|
|
176
235
|
try {
|
|
177
236
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
178
237
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -202,9 +261,10 @@ export class Frontend extends EventEmitter {
|
|
|
202
261
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
203
262
|
}
|
|
204
263
|
if (hasParameter('mtls')) {
|
|
205
|
-
httpsServerOptions.requestCert = true;
|
|
206
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
264
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
265
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
207
266
|
}
|
|
267
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
208
268
|
const https = await import('node:https');
|
|
209
269
|
try {
|
|
210
270
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -215,6 +275,7 @@ export class Frontend extends EventEmitter {
|
|
|
215
275
|
this.emit('server_error', error);
|
|
216
276
|
return;
|
|
217
277
|
}
|
|
278
|
+
// Listen on the specified port
|
|
218
279
|
if (hasParameter('ingress')) {
|
|
219
280
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
220
281
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -246,16 +307,18 @@ export class Frontend extends EventEmitter {
|
|
|
246
307
|
return;
|
|
247
308
|
});
|
|
248
309
|
}
|
|
310
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
249
311
|
const ws = await import('ws');
|
|
250
312
|
this.log.debug(`Creating WebSocketServer...`);
|
|
251
313
|
this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
252
314
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
253
315
|
const clientIp = request.socket.remoteAddress;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
316
|
+
// Set the global logger callback for the WebSocketServer
|
|
317
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
318
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
319
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
320
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
321
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
259
322
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
260
323
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
261
324
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -277,6 +340,7 @@ export class Frontend extends EventEmitter {
|
|
|
277
340
|
}
|
|
278
341
|
});
|
|
279
342
|
ws.on('error', (error) => {
|
|
343
|
+
// istanbul ignore next
|
|
280
344
|
this.log.error(`WebSocket client error: ${error}`);
|
|
281
345
|
});
|
|
282
346
|
});
|
|
@@ -290,6 +354,7 @@ export class Frontend extends EventEmitter {
|
|
|
290
354
|
this.webSocketServer.on('error', (ws, error) => {
|
|
291
355
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
292
356
|
});
|
|
357
|
+
// Subscribe to cli events
|
|
293
358
|
cliEmitter.removeAllListeners();
|
|
294
359
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
295
360
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -300,6 +365,8 @@ export class Frontend extends EventEmitter {
|
|
|
300
365
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
301
366
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
302
367
|
});
|
|
368
|
+
// Endpoint to validate login code
|
|
369
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
303
370
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
304
371
|
const { password } = req.body;
|
|
305
372
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -318,23 +385,27 @@ export class Frontend extends EventEmitter {
|
|
|
318
385
|
this.log.warn('/api/login error wrong password');
|
|
319
386
|
res.json({ valid: false });
|
|
320
387
|
}
|
|
388
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
321
389
|
}
|
|
322
390
|
catch (error) {
|
|
323
391
|
this.log.error('/api/login error getting password');
|
|
324
392
|
res.json({ valid: false });
|
|
325
393
|
}
|
|
326
394
|
});
|
|
395
|
+
// Endpoint to provide health check for docker
|
|
327
396
|
this.expressApp.get('/health', (req, res) => {
|
|
328
397
|
this.log.debug('Express received /health');
|
|
329
398
|
const healthStatus = {
|
|
330
|
-
status: 'ok',
|
|
331
|
-
uptime: process.uptime(),
|
|
332
|
-
timestamp: new Date().toISOString(),
|
|
399
|
+
status: 'ok', // Indicate service is healthy
|
|
400
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
401
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
333
402
|
};
|
|
334
403
|
res.status(200).json(healthStatus);
|
|
335
404
|
});
|
|
405
|
+
// Endpoint to provide memory usage details
|
|
336
406
|
this.expressApp.get('/memory', async (req, res) => {
|
|
337
407
|
this.log.debug('Express received /memory');
|
|
408
|
+
// Memory usage from process
|
|
338
409
|
const memoryUsageRaw = process.memoryUsage();
|
|
339
410
|
const memoryUsage = {
|
|
340
411
|
rss: formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -343,10 +414,13 @@ export class Frontend extends EventEmitter {
|
|
|
343
414
|
external: formatMemoryUsage(memoryUsageRaw.external),
|
|
344
415
|
arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
345
416
|
};
|
|
417
|
+
// V8 heap statistics
|
|
346
418
|
const { default: v8 } = await import('node:v8');
|
|
347
419
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
348
420
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
421
|
+
// Format heapStats
|
|
349
422
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
|
|
423
|
+
// Format heapSpaces
|
|
350
424
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
351
425
|
...space,
|
|
352
426
|
space_size: formatMemoryUsage(space.space_size),
|
|
@@ -365,18 +439,22 @@ export class Frontend extends EventEmitter {
|
|
|
365
439
|
};
|
|
366
440
|
res.status(200).json(memoryReport);
|
|
367
441
|
});
|
|
442
|
+
// Endpoint to provide settings
|
|
368
443
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
369
444
|
this.log.debug('The frontend sent /api/settings');
|
|
370
445
|
res.json(await this.getApiSettings());
|
|
371
446
|
});
|
|
447
|
+
// Endpoint to provide plugins
|
|
372
448
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
373
449
|
this.log.debug('The frontend sent /api/plugins');
|
|
374
450
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
375
451
|
});
|
|
452
|
+
// Endpoint to provide devices
|
|
376
453
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
377
454
|
this.log.debug('The frontend sent /api/devices');
|
|
378
455
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
379
456
|
});
|
|
457
|
+
// Endpoint to view the matterbridge log
|
|
380
458
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
381
459
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
382
460
|
try {
|
|
@@ -390,6 +468,7 @@ export class Frontend extends EventEmitter {
|
|
|
390
468
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
391
469
|
}
|
|
392
470
|
});
|
|
471
|
+
// Endpoint to view the matter.js log
|
|
393
472
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
394
473
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
395
474
|
try {
|
|
@@ -403,6 +482,7 @@ export class Frontend extends EventEmitter {
|
|
|
403
482
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
404
483
|
}
|
|
405
484
|
});
|
|
485
|
+
// Endpoint to view the diagnostic.log
|
|
406
486
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
407
487
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
408
488
|
await this.generateDiagnostic();
|
|
@@ -413,10 +493,13 @@ export class Frontend extends EventEmitter {
|
|
|
413
493
|
res.send(data.slice(29));
|
|
414
494
|
}
|
|
415
495
|
catch (error) {
|
|
496
|
+
// istanbul ignore next
|
|
416
497
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
498
|
+
// istanbul ignore next
|
|
417
499
|
res.status(500).send('Error reading diagnostic log file.');
|
|
418
500
|
}
|
|
419
501
|
});
|
|
502
|
+
// Endpoint to download the diagnostic.log
|
|
420
503
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
421
504
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
422
505
|
await this.generateDiagnostic();
|
|
@@ -427,16 +510,19 @@ export class Frontend extends EventEmitter {
|
|
|
427
510
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
428
511
|
}
|
|
429
512
|
catch (error) {
|
|
513
|
+
// istanbul ignore next
|
|
430
514
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
431
515
|
}
|
|
432
516
|
res.type('text/plain');
|
|
433
517
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
518
|
+
/* istanbul ignore if */
|
|
434
519
|
if (error) {
|
|
435
520
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
436
521
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
437
522
|
}
|
|
438
523
|
});
|
|
439
524
|
});
|
|
525
|
+
// Endpoint to view the history.html
|
|
440
526
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
441
527
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
442
528
|
try {
|
|
@@ -450,6 +536,7 @@ export class Frontend extends EventEmitter {
|
|
|
450
536
|
res.status(500).send('Error reading history file.');
|
|
451
537
|
}
|
|
452
538
|
});
|
|
539
|
+
// Endpoint to download the history.html
|
|
453
540
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
454
541
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
455
542
|
try {
|
|
@@ -459,6 +546,7 @@ export class Frontend extends EventEmitter {
|
|
|
459
546
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
460
547
|
res.type('text/plain');
|
|
461
548
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
549
|
+
/* istanbul ignore if */
|
|
462
550
|
if (error) {
|
|
463
551
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
464
552
|
res.status(500).send('Error downloading history file');
|
|
@@ -470,6 +558,7 @@ export class Frontend extends EventEmitter {
|
|
|
470
558
|
res.status(500).send('Error reading history file.');
|
|
471
559
|
}
|
|
472
560
|
});
|
|
561
|
+
// Endpoint to view the shelly log
|
|
473
562
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
474
563
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
475
564
|
try {
|
|
@@ -483,6 +572,7 @@ export class Frontend extends EventEmitter {
|
|
|
483
572
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
484
573
|
}
|
|
485
574
|
});
|
|
575
|
+
// Endpoint to download the matterbridge log
|
|
486
576
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
487
577
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
488
578
|
const fs = await import('node:fs');
|
|
@@ -497,12 +587,14 @@ export class Frontend extends EventEmitter {
|
|
|
497
587
|
}
|
|
498
588
|
res.type('text/plain');
|
|
499
589
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
590
|
+
/* istanbul ignore if */
|
|
500
591
|
if (error) {
|
|
501
592
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
502
593
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
503
594
|
}
|
|
504
595
|
});
|
|
505
596
|
});
|
|
597
|
+
// Endpoint to download the matter log
|
|
506
598
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
507
599
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
508
600
|
const fs = await import('node:fs');
|
|
@@ -517,12 +609,14 @@ export class Frontend extends EventEmitter {
|
|
|
517
609
|
}
|
|
518
610
|
res.type('text/plain');
|
|
519
611
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
612
|
+
/* istanbul ignore if */
|
|
520
613
|
if (error) {
|
|
521
614
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
522
615
|
res.status(500).send('Error downloading the matter log file');
|
|
523
616
|
}
|
|
524
617
|
});
|
|
525
618
|
});
|
|
619
|
+
// Endpoint to download the shelly log
|
|
526
620
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
527
621
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
528
622
|
const fs = await import('node:fs');
|
|
@@ -537,75 +631,91 @@ export class Frontend extends EventEmitter {
|
|
|
537
631
|
}
|
|
538
632
|
res.type('text/plain');
|
|
539
633
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
634
|
+
/* istanbul ignore if */
|
|
540
635
|
if (error) {
|
|
541
636
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
542
637
|
res.status(500).send('Error downloading Shelly system log file');
|
|
543
638
|
}
|
|
544
639
|
});
|
|
545
640
|
});
|
|
641
|
+
// Endpoint to download the matterbridge storage directory
|
|
546
642
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
547
643
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
548
644
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
549
645
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
646
|
+
/* istanbul ignore if */
|
|
550
647
|
if (error) {
|
|
551
648
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
552
649
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
553
650
|
}
|
|
554
651
|
});
|
|
555
652
|
});
|
|
653
|
+
// Endpoint to download the matter storage file
|
|
556
654
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
557
655
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
558
656
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
559
657
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
658
|
+
/* istanbul ignore if */
|
|
560
659
|
if (error) {
|
|
561
660
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
562
661
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
563
662
|
}
|
|
564
663
|
});
|
|
565
664
|
});
|
|
665
|
+
// Endpoint to download the matterbridge plugin directory
|
|
566
666
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
567
667
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
568
668
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
569
669
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
670
|
+
/* istanbul ignore if */
|
|
570
671
|
if (error) {
|
|
571
672
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
572
673
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
573
674
|
}
|
|
574
675
|
});
|
|
575
676
|
});
|
|
677
|
+
// Endpoint to download the matterbridge plugin config files
|
|
576
678
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
577
679
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
578
680
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
579
681
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
682
|
+
/* istanbul ignore if */
|
|
580
683
|
if (error) {
|
|
581
684
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
582
685
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
583
686
|
}
|
|
584
687
|
});
|
|
585
688
|
});
|
|
689
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
586
690
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
587
691
|
this.log.debug('The frontend sent /api/download-backup');
|
|
588
692
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
693
|
+
/* istanbul ignore if */
|
|
589
694
|
if (error) {
|
|
590
695
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
591
696
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
592
697
|
}
|
|
593
698
|
});
|
|
594
699
|
});
|
|
700
|
+
// Endpoint to upload a package
|
|
595
701
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
596
702
|
const { filename } = req.body;
|
|
597
703
|
const file = req.file;
|
|
704
|
+
/* istanbul ignore if */
|
|
598
705
|
if (!file || !filename) {
|
|
599
706
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
600
707
|
res.status(400).send('Invalid request: file and filename are required');
|
|
601
708
|
return;
|
|
602
709
|
}
|
|
603
710
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
711
|
+
// Define the path where the plugin file will be saved
|
|
604
712
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
605
713
|
try {
|
|
714
|
+
// Move the uploaded file to the specified path
|
|
606
715
|
const fs = await import('node:fs');
|
|
607
716
|
await fs.promises.rename(file.path, filePath);
|
|
608
717
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
718
|
+
// Install the plugin package
|
|
609
719
|
if (filename.endsWith('.tgz')) {
|
|
610
720
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
611
721
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -625,6 +735,7 @@ export class Frontend extends EventEmitter {
|
|
|
625
735
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
626
736
|
}
|
|
627
737
|
});
|
|
738
|
+
// Fallback for routing (must be the last route)
|
|
628
739
|
this.expressApp.use((req, res) => {
|
|
629
740
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
630
741
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -634,13 +745,16 @@ export class Frontend extends EventEmitter {
|
|
|
634
745
|
async stop() {
|
|
635
746
|
this.log.debug('Stopping the frontend...');
|
|
636
747
|
const ws = await import('ws');
|
|
748
|
+
// Remove listeners from the express app
|
|
637
749
|
if (this.expressApp) {
|
|
638
750
|
this.expressApp.removeAllListeners();
|
|
639
751
|
this.expressApp = undefined;
|
|
640
752
|
this.log.debug('Frontend app closed successfully');
|
|
641
753
|
}
|
|
754
|
+
// Close the WebSocket server
|
|
642
755
|
if (this.webSocketServer) {
|
|
643
756
|
this.log.debug('Closing WebSocket server...');
|
|
757
|
+
// Close all active connections
|
|
644
758
|
this.webSocketServer.clients.forEach((client) => {
|
|
645
759
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
646
760
|
client.close();
|
|
@@ -649,6 +763,7 @@ export class Frontend extends EventEmitter {
|
|
|
649
763
|
await withTimeout(new Promise((resolve) => {
|
|
650
764
|
this.webSocketServer?.close((error) => {
|
|
651
765
|
if (error) {
|
|
766
|
+
// istanbul ignore next
|
|
652
767
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
653
768
|
}
|
|
654
769
|
else {
|
|
@@ -661,8 +776,27 @@ export class Frontend extends EventEmitter {
|
|
|
661
776
|
this.webSocketServer.removeAllListeners();
|
|
662
777
|
this.webSocketServer = undefined;
|
|
663
778
|
}
|
|
779
|
+
// Close the http server
|
|
664
780
|
if (this.httpServer) {
|
|
665
781
|
this.log.debug('Closing http server...');
|
|
782
|
+
/*
|
|
783
|
+
await withTimeout(
|
|
784
|
+
new Promise<void>((resolve) => {
|
|
785
|
+
this.httpServer?.close((error) => {
|
|
786
|
+
if (error) {
|
|
787
|
+
// istanbul ignore next
|
|
788
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
789
|
+
} else {
|
|
790
|
+
this.log.debug('Http server closed successfully');
|
|
791
|
+
this.emit('server_stopped');
|
|
792
|
+
}
|
|
793
|
+
resolve();
|
|
794
|
+
});
|
|
795
|
+
}),
|
|
796
|
+
5000,
|
|
797
|
+
false,
|
|
798
|
+
);
|
|
799
|
+
*/
|
|
666
800
|
this.httpServer.close();
|
|
667
801
|
this.log.debug('Http server closed successfully');
|
|
668
802
|
this.listening = false;
|
|
@@ -671,8 +805,27 @@ export class Frontend extends EventEmitter {
|
|
|
671
805
|
this.httpServer = undefined;
|
|
672
806
|
this.log.debug('Frontend http server closed successfully');
|
|
673
807
|
}
|
|
808
|
+
// Close the https server
|
|
674
809
|
if (this.httpsServer) {
|
|
675
810
|
this.log.debug('Closing https server...');
|
|
811
|
+
/*
|
|
812
|
+
await withTimeout(
|
|
813
|
+
new Promise<void>((resolve) => {
|
|
814
|
+
this.httpsServer?.close((error) => {
|
|
815
|
+
if (error) {
|
|
816
|
+
// istanbul ignore next
|
|
817
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
818
|
+
} else {
|
|
819
|
+
this.log.debug('Https server closed successfully');
|
|
820
|
+
this.emit('server_stopped');
|
|
821
|
+
}
|
|
822
|
+
resolve();
|
|
823
|
+
});
|
|
824
|
+
}),
|
|
825
|
+
5000,
|
|
826
|
+
false,
|
|
827
|
+
);
|
|
828
|
+
*/
|
|
676
829
|
this.httpsServer.close();
|
|
677
830
|
this.log.debug('Https server closed successfully');
|
|
678
831
|
this.listening = false;
|
|
@@ -683,7 +836,13 @@ export class Frontend extends EventEmitter {
|
|
|
683
836
|
}
|
|
684
837
|
this.log.debug('Frontend stopped successfully');
|
|
685
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Retrieves the api settings data.
|
|
841
|
+
*
|
|
842
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
843
|
+
*/
|
|
686
844
|
async getApiSettings() {
|
|
845
|
+
// Update the variable system information properties
|
|
687
846
|
this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
688
847
|
this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
689
848
|
this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
@@ -693,6 +852,7 @@ export class Frontend extends EventEmitter {
|
|
|
693
852
|
this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
694
853
|
this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
695
854
|
this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
855
|
+
// Create the matterbridge information
|
|
696
856
|
const info = {
|
|
697
857
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
698
858
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -728,9 +888,15 @@ export class Frontend extends EventEmitter {
|
|
|
728
888
|
};
|
|
729
889
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
730
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Retrieves the reachable attribute.
|
|
893
|
+
*
|
|
894
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
895
|
+
* @returns {boolean} The reachable attribute.
|
|
896
|
+
*/
|
|
731
897
|
getReachability(device) {
|
|
732
898
|
if (this.matterbridge.hasCleanupStarted)
|
|
733
|
-
return false;
|
|
899
|
+
return false; // Skip if cleanup has started
|
|
734
900
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
735
901
|
return false;
|
|
736
902
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -741,9 +907,15 @@ export class Frontend extends EventEmitter {
|
|
|
741
907
|
return true;
|
|
742
908
|
return false;
|
|
743
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Retrieves the power source attribute.
|
|
912
|
+
*
|
|
913
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
914
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
915
|
+
*/
|
|
744
916
|
getPowerSource(endpoint) {
|
|
745
917
|
if (this.matterbridge.hasCleanupStarted)
|
|
746
|
-
return;
|
|
918
|
+
return; // Skip if cleanup has started
|
|
747
919
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
748
920
|
return undefined;
|
|
749
921
|
const powerSource = (device) => {
|
|
@@ -758,16 +930,25 @@ export class Frontend extends EventEmitter {
|
|
|
758
930
|
}
|
|
759
931
|
return;
|
|
760
932
|
};
|
|
933
|
+
// Root endpoint
|
|
761
934
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
762
935
|
return powerSource(endpoint);
|
|
936
|
+
// Child endpoints
|
|
763
937
|
for (const child of endpoint.getChildEndpoints()) {
|
|
764
938
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
765
939
|
return powerSource(child);
|
|
766
940
|
}
|
|
767
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Retrieves the cluster text description from a given device.
|
|
944
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
945
|
+
*
|
|
946
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
947
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
948
|
+
*/
|
|
768
949
|
getClusterTextFromDevice(device) {
|
|
769
950
|
if (this.matterbridge.hasCleanupStarted)
|
|
770
|
-
return '';
|
|
951
|
+
return ''; // Skip if cleanup has started
|
|
771
952
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
772
953
|
return '';
|
|
773
954
|
const getUserLabel = (device) => {
|
|
@@ -777,6 +958,7 @@ export class Frontend extends EventEmitter {
|
|
|
777
958
|
if (composed)
|
|
778
959
|
return 'Composed: ' + composed.value;
|
|
779
960
|
}
|
|
961
|
+
// istanbul ignore next cause is not reachable
|
|
780
962
|
return '';
|
|
781
963
|
};
|
|
782
964
|
const getFixedLabel = (device) => {
|
|
@@ -786,11 +968,13 @@ export class Frontend extends EventEmitter {
|
|
|
786
968
|
if (composed)
|
|
787
969
|
return 'Composed: ' + composed.value;
|
|
788
970
|
}
|
|
971
|
+
// istanbul ignore next cause is not reacheable
|
|
789
972
|
return '';
|
|
790
973
|
};
|
|
791
974
|
let attributes = '';
|
|
792
975
|
let supportedModes = [];
|
|
793
976
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
977
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
794
978
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
795
979
|
return;
|
|
796
980
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -880,11 +1064,17 @@ export class Frontend extends EventEmitter {
|
|
|
880
1064
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
881
1065
|
attributes += `${getUserLabel(device)} `;
|
|
882
1066
|
});
|
|
1067
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
883
1068
|
return attributes.trimStart().trimEnd();
|
|
884
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1072
|
+
*
|
|
1073
|
+
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1074
|
+
*/
|
|
885
1075
|
getPlugins() {
|
|
886
1076
|
if (this.matterbridge.hasCleanupStarted)
|
|
887
|
-
return [];
|
|
1077
|
+
return []; // Skip if cleanup has started
|
|
888
1078
|
const plugins = [];
|
|
889
1079
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
890
1080
|
plugins.push({
|
|
@@ -912,18 +1102,27 @@ export class Frontend extends EventEmitter {
|
|
|
912
1102
|
schemaJson: plugin.schemaJson,
|
|
913
1103
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
914
1104
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1105
|
+
// Childbridge mode specific data
|
|
915
1106
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
916
1107
|
});
|
|
917
1108
|
}
|
|
918
1109
|
return plugins;
|
|
919
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Retrieves the devices from Matterbridge.
|
|
1113
|
+
*
|
|
1114
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1115
|
+
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1116
|
+
*/
|
|
920
1117
|
getDevices(pluginName) {
|
|
921
1118
|
if (this.matterbridge.hasCleanupStarted)
|
|
922
|
-
return [];
|
|
1119
|
+
return []; // Skip if cleanup has started
|
|
923
1120
|
const devices = [];
|
|
924
1121
|
for (const device of this.matterbridge.devices.array()) {
|
|
1122
|
+
// Filter by pluginName if provided
|
|
925
1123
|
if (pluginName && pluginName !== device.plugin)
|
|
926
1124
|
continue;
|
|
1125
|
+
// Check if the device has the required properties
|
|
927
1126
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
928
1127
|
continue;
|
|
929
1128
|
devices.push({
|
|
@@ -943,24 +1142,39 @@ export class Frontend extends EventEmitter {
|
|
|
943
1142
|
}
|
|
944
1143
|
return devices;
|
|
945
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1147
|
+
*
|
|
1148
|
+
* Response for /api/clusters
|
|
1149
|
+
*
|
|
1150
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1151
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1152
|
+
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1153
|
+
*/
|
|
946
1154
|
getClusters(pluginName, endpointNumber) {
|
|
947
1155
|
if (this.matterbridge.hasCleanupStarted)
|
|
948
|
-
return;
|
|
1156
|
+
return; // Skip if cleanup has started
|
|
949
1157
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
950
1158
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
951
1159
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
952
1160
|
return;
|
|
953
1161
|
}
|
|
1162
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1163
|
+
// Get the device types from the main endpoint
|
|
954
1164
|
const deviceTypes = [];
|
|
955
1165
|
const clusters = [];
|
|
956
1166
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
957
1167
|
deviceTypes.push(d.deviceType);
|
|
958
1168
|
});
|
|
1169
|
+
// Get the clusters from the main endpoint
|
|
959
1170
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
960
1171
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
961
1172
|
return;
|
|
962
1173
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
963
1174
|
return;
|
|
1175
|
+
// console.log(
|
|
1176
|
+
// `${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}`,
|
|
1177
|
+
// );
|
|
964
1178
|
clusters.push({
|
|
965
1179
|
endpoint: endpoint.number.toString(),
|
|
966
1180
|
number: endpoint.number,
|
|
@@ -974,12 +1188,19 @@ export class Frontend extends EventEmitter {
|
|
|
974
1188
|
attributeLocalValue: attributeValue,
|
|
975
1189
|
});
|
|
976
1190
|
});
|
|
1191
|
+
// Get the child endpoints
|
|
977
1192
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1193
|
+
// if (childEndpoints.length === 0) {
|
|
1194
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1195
|
+
// }
|
|
978
1196
|
childEndpoints.forEach((childEndpoint) => {
|
|
1197
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
979
1198
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
980
1199
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
981
1200
|
return;
|
|
982
1201
|
}
|
|
1202
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1203
|
+
// Get the device types of the child endpoint
|
|
983
1204
|
const deviceTypes = [];
|
|
984
1205
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
985
1206
|
deviceTypes.push(d.deviceType);
|
|
@@ -989,6 +1210,9 @@ export class Frontend extends EventEmitter {
|
|
|
989
1210
|
return;
|
|
990
1211
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
991
1212
|
return;
|
|
1213
|
+
// console.log(
|
|
1214
|
+
// `${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}`,
|
|
1215
|
+
// );
|
|
992
1216
|
clusters.push({
|
|
993
1217
|
endpoint: childEndpoint.number.toString(),
|
|
994
1218
|
number: childEndpoint.number,
|
|
@@ -1008,6 +1232,7 @@ export class Frontend extends EventEmitter {
|
|
|
1008
1232
|
async generateDiagnostic() {
|
|
1009
1233
|
this.log.debug('Generating diagnostic...');
|
|
1010
1234
|
const serverNodes = [];
|
|
1235
|
+
// istanbul ignore else
|
|
1011
1236
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1012
1237
|
if (this.matterbridge.serverNode)
|
|
1013
1238
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1018,6 +1243,7 @@ export class Frontend extends EventEmitter {
|
|
|
1018
1243
|
serverNodes.push(plugin.serverNode);
|
|
1019
1244
|
}
|
|
1020
1245
|
}
|
|
1246
|
+
// istanbul ignore next
|
|
1021
1247
|
for (const device of this.matterbridge.devices.array()) {
|
|
1022
1248
|
if (device.serverNode)
|
|
1023
1249
|
serverNodes.push(device.serverNode);
|
|
@@ -1041,8 +1267,15 @@ export class Frontend extends EventEmitter {
|
|
|
1041
1267
|
values: [...serverNodes],
|
|
1042
1268
|
})));
|
|
1043
1269
|
delete Logger.destinations.diagnostic;
|
|
1044
|
-
await wait(500);
|
|
1270
|
+
await wait(500); // Wait for the log to be written
|
|
1045
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1274
|
+
*
|
|
1275
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1276
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1277
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1278
|
+
*/
|
|
1046
1279
|
async wsMessageHandler(client, message) {
|
|
1047
1280
|
let data;
|
|
1048
1281
|
const sendResponse = (data) => {
|
|
@@ -1062,7 +1295,7 @@ export class Frontend extends EventEmitter {
|
|
|
1062
1295
|
};
|
|
1063
1296
|
try {
|
|
1064
1297
|
data = JSON.parse(message.toString());
|
|
1065
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1298
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
1066
1299
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1067
1300
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1068
1301
|
return;
|
|
@@ -1136,6 +1369,7 @@ export class Frontend extends EventEmitter {
|
|
|
1136
1369
|
return;
|
|
1137
1370
|
})
|
|
1138
1371
|
.catch((_error) => {
|
|
1372
|
+
//
|
|
1139
1373
|
});
|
|
1140
1374
|
}
|
|
1141
1375
|
else {
|
|
@@ -1183,6 +1417,7 @@ export class Frontend extends EventEmitter {
|
|
|
1183
1417
|
return;
|
|
1184
1418
|
})
|
|
1185
1419
|
.catch((_error) => {
|
|
1420
|
+
//
|
|
1186
1421
|
});
|
|
1187
1422
|
}
|
|
1188
1423
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1208,6 +1443,7 @@ export class Frontend extends EventEmitter {
|
|
|
1208
1443
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1209
1444
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1210
1445
|
if (plugin.serverNode) {
|
|
1446
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1211
1447
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1212
1448
|
plugin.serverNode = undefined;
|
|
1213
1449
|
}
|
|
@@ -1217,18 +1453,20 @@ export class Frontend extends EventEmitter {
|
|
|
1217
1453
|
this.matterbridge.devices.remove(device);
|
|
1218
1454
|
}
|
|
1219
1455
|
}
|
|
1456
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1220
1457
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1221
1458
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1222
1459
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1223
|
-
plugin.restartRequired = false;
|
|
1460
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1224
1461
|
let needRestart = 0;
|
|
1225
1462
|
for (const plugin of this.matterbridge.plugins) {
|
|
1226
1463
|
if (plugin.restartRequired)
|
|
1227
1464
|
needRestart++;
|
|
1228
1465
|
}
|
|
1229
1466
|
if (needRestart === 0) {
|
|
1230
|
-
this.wssSendRestartNotRequired(true);
|
|
1467
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1231
1468
|
}
|
|
1469
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1232
1470
|
if (plugin.serverNode)
|
|
1233
1471
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1234
1472
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1493,22 +1731,22 @@ export class Frontend extends EventEmitter {
|
|
|
1493
1731
|
if (isValidString(data.params.value, 4)) {
|
|
1494
1732
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1495
1733
|
if (data.params.value === 'Debug') {
|
|
1496
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1734
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1497
1735
|
}
|
|
1498
1736
|
else if (data.params.value === 'Info') {
|
|
1499
|
-
await this.matterbridge.setLogLevel("info");
|
|
1737
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1500
1738
|
}
|
|
1501
1739
|
else if (data.params.value === 'Notice') {
|
|
1502
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1740
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1503
1741
|
}
|
|
1504
1742
|
else if (data.params.value === 'Warn') {
|
|
1505
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1743
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1506
1744
|
}
|
|
1507
1745
|
else if (data.params.value === 'Error') {
|
|
1508
|
-
await this.matterbridge.setLogLevel("error");
|
|
1746
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1509
1747
|
}
|
|
1510
1748
|
else if (data.params.value === 'Fatal') {
|
|
1511
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1749
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1512
1750
|
}
|
|
1513
1751
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1514
1752
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1519,6 +1757,7 @@ export class Frontend extends EventEmitter {
|
|
|
1519
1757
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1520
1758
|
this.matterbridge.fileLogger = data.params.value;
|
|
1521
1759
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1760
|
+
// Create the file logger for matterbridge
|
|
1522
1761
|
if (data.params.value)
|
|
1523
1762
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1524
1763
|
else
|
|
@@ -1547,6 +1786,14 @@ export class Frontend extends EventEmitter {
|
|
|
1547
1786
|
else if (data.params.value === 'Fatal') {
|
|
1548
1787
|
Logger.level = MatterLogLevel.FATAL;
|
|
1549
1788
|
}
|
|
1789
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1790
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1791
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1792
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1793
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1794
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1795
|
+
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1796
|
+
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1550
1797
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
1551
1798
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1552
1799
|
}
|
|
@@ -1596,6 +1843,7 @@ export class Frontend extends EventEmitter {
|
|
|
1596
1843
|
}
|
|
1597
1844
|
break;
|
|
1598
1845
|
case 'setmatterport':
|
|
1846
|
+
// eslint-disable-next-line no-case-declarations
|
|
1599
1847
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1600
1848
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1601
1849
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1615,6 +1863,7 @@ export class Frontend extends EventEmitter {
|
|
|
1615
1863
|
}
|
|
1616
1864
|
break;
|
|
1617
1865
|
case 'setmatterdiscriminator':
|
|
1866
|
+
// eslint-disable-next-line no-case-declarations
|
|
1618
1867
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1619
1868
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1620
1869
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1634,6 +1883,7 @@ export class Frontend extends EventEmitter {
|
|
|
1634
1883
|
}
|
|
1635
1884
|
break;
|
|
1636
1885
|
case 'setmatterpasscode':
|
|
1886
|
+
// eslint-disable-next-line no-case-declarations
|
|
1637
1887
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1638
1888
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1639
1889
|
this.matterbridge.passcode = passcode;
|
|
@@ -1679,15 +1929,19 @@ export class Frontend extends EventEmitter {
|
|
|
1679
1929
|
return;
|
|
1680
1930
|
}
|
|
1681
1931
|
const config = plugin.configJson;
|
|
1932
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1682
1933
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1934
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1683
1935
|
if (select === 'serial')
|
|
1684
1936
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1685
1937
|
if (select === 'name')
|
|
1686
1938
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1687
1939
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1940
|
+
// Remove postfix from the serial if it exists
|
|
1688
1941
|
if (config.postfix) {
|
|
1689
1942
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1690
1943
|
}
|
|
1944
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1691
1945
|
if (isValidArray(config.whiteList, 1)) {
|
|
1692
1946
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1693
1947
|
config.whiteList.push(data.params.serial);
|
|
@@ -1696,6 +1950,7 @@ export class Frontend extends EventEmitter {
|
|
|
1696
1950
|
config.whiteList.push(data.params.name);
|
|
1697
1951
|
}
|
|
1698
1952
|
}
|
|
1953
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1699
1954
|
if (isValidArray(config.blackList, 1)) {
|
|
1700
1955
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1701
1956
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1723,7 +1978,9 @@ export class Frontend extends EventEmitter {
|
|
|
1723
1978
|
return;
|
|
1724
1979
|
}
|
|
1725
1980
|
const config = plugin.configJson;
|
|
1981
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1726
1982
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1983
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1727
1984
|
if (select === 'serial')
|
|
1728
1985
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1729
1986
|
if (select === 'name')
|
|
@@ -1732,6 +1989,7 @@ export class Frontend extends EventEmitter {
|
|
|
1732
1989
|
if (config.postfix) {
|
|
1733
1990
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1734
1991
|
}
|
|
1992
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1735
1993
|
if (isValidArray(config.whiteList, 1)) {
|
|
1736
1994
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1737
1995
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1740,6 +1998,7 @@ export class Frontend extends EventEmitter {
|
|
|
1740
1998
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1741
1999
|
}
|
|
1742
2000
|
}
|
|
2001
|
+
// Add the serial to the blackList
|
|
1743
2002
|
if (isValidArray(config.blackList)) {
|
|
1744
2003
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1745
2004
|
config.blackList.push(data.params.serial);
|
|
@@ -1762,6 +2021,7 @@ export class Frontend extends EventEmitter {
|
|
|
1762
2021
|
}
|
|
1763
2022
|
}
|
|
1764
2023
|
else {
|
|
2024
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1765
2025
|
const localData = data;
|
|
1766
2026
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1767
2027
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1771,23 +2031,46 @@ export class Frontend extends EventEmitter {
|
|
|
1771
2031
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1772
2032
|
}
|
|
1773
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2036
|
+
*
|
|
2037
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2038
|
+
* @param {string} time - The time string of the message
|
|
2039
|
+
* @param {string} name - The logger name of the message
|
|
2040
|
+
* @param {string} message - The content of the message.
|
|
2041
|
+
*
|
|
2042
|
+
* @remarks
|
|
2043
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2044
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2045
|
+
* The function sends the message to all connected clients.
|
|
2046
|
+
*/
|
|
1774
2047
|
wssSendLogMessage(level, time, name, message) {
|
|
1775
2048
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1776
2049
|
return;
|
|
1777
2050
|
if (!level || !time || !name || !message)
|
|
1778
2051
|
return;
|
|
2052
|
+
// Remove ANSI escape codes from the message
|
|
2053
|
+
// eslint-disable-next-line no-control-regex
|
|
1779
2054
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2055
|
+
// Remove leading asterisks from the message
|
|
1780
2056
|
message = message.replace(/^\*+/, '');
|
|
2057
|
+
// Replace all occurrences of \t and \n
|
|
1781
2058
|
message = message.replace(/[\t\n]/g, '');
|
|
2059
|
+
// Remove non-printable characters
|
|
2060
|
+
// eslint-disable-next-line no-control-regex
|
|
1782
2061
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2062
|
+
// Replace all occurrences of \" with "
|
|
1783
2063
|
message = message.replace(/\\"/g, '"');
|
|
2064
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1784
2065
|
const maxContinuousLength = 100;
|
|
1785
2066
|
const keepStartLength = 20;
|
|
1786
2067
|
const keepEndLength = 20;
|
|
2068
|
+
// Split the message into words
|
|
1787
2069
|
if (level !== 'spawn') {
|
|
1788
2070
|
message = message
|
|
1789
2071
|
.split(' ')
|
|
1790
2072
|
.map((word) => {
|
|
2073
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1791
2074
|
if (word.length > maxContinuousLength) {
|
|
1792
2075
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1793
2076
|
}
|
|
@@ -1795,14 +2078,34 @@ export class Frontend extends EventEmitter {
|
|
|
1795
2078
|
})
|
|
1796
2079
|
.join(' ');
|
|
1797
2080
|
}
|
|
2081
|
+
// Send the message to all connected clients
|
|
1798
2082
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1799
2083
|
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2086
|
+
*
|
|
2087
|
+
* @param {string} changed - The changed value.
|
|
2088
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2089
|
+
* possible values for changed:
|
|
2090
|
+
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2091
|
+
* - 'plugins'
|
|
2092
|
+
* - 'devices'
|
|
2093
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2094
|
+
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2095
|
+
*/
|
|
1800
2096
|
wssSendRefreshRequired(changed, params) {
|
|
1801
2097
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1802
2098
|
return;
|
|
1803
2099
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2100
|
+
// Send the message to all connected clients
|
|
1804
2101
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1805
2102
|
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2105
|
+
*
|
|
2106
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2107
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2108
|
+
*/
|
|
1806
2109
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1807
2110
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1808
2111
|
return;
|
|
@@ -1811,8 +2114,14 @@ export class Frontend extends EventEmitter {
|
|
|
1811
2114
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
1812
2115
|
if (snackbar === true)
|
|
1813
2116
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2117
|
+
// Send the message to all connected clients
|
|
1814
2118
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1815
2119
|
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2122
|
+
*
|
|
2123
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2124
|
+
*/
|
|
1816
2125
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1817
2126
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1818
2127
|
return;
|
|
@@ -1820,57 +2129,133 @@ export class Frontend extends EventEmitter {
|
|
|
1820
2129
|
this.matterbridge.restartRequired = false;
|
|
1821
2130
|
if (snackbar === true)
|
|
1822
2131
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2132
|
+
// Send the message to all connected clients
|
|
1823
2133
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1824
2134
|
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2137
|
+
*
|
|
2138
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2139
|
+
*/
|
|
1825
2140
|
wssSendUpdateRequired(devVersion = false) {
|
|
1826
2141
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1827
2142
|
return;
|
|
1828
2143
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1829
2144
|
this.matterbridge.updateRequired = true;
|
|
2145
|
+
// Send the message to all connected clients
|
|
1830
2146
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1831
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Sends a cpu update message to all connected clients.
|
|
2150
|
+
*
|
|
2151
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2152
|
+
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2153
|
+
*/
|
|
1832
2154
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
1833
2155
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1834
2156
|
return;
|
|
1835
2157
|
if (hasParameter('debug'))
|
|
1836
2158
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2159
|
+
// Send the message to all connected clients
|
|
1837
2160
|
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 } });
|
|
1838
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Sends a memory update message to all connected clients.
|
|
2164
|
+
*
|
|
2165
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2166
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2167
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2168
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2169
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2170
|
+
* @param {string} external - The external memory in bytes.
|
|
2171
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2172
|
+
*/
|
|
1839
2173
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1840
2174
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1841
2175
|
return;
|
|
1842
2176
|
if (hasParameter('debug'))
|
|
1843
2177
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2178
|
+
// Send the message to all connected clients
|
|
1844
2179
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1845
2180
|
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Sends an uptime update message to all connected clients.
|
|
2183
|
+
*
|
|
2184
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2185
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2186
|
+
*/
|
|
1846
2187
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1847
2188
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1848
2189
|
return;
|
|
1849
2190
|
if (hasParameter('debug'))
|
|
1850
2191
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2192
|
+
// Send the message to all connected clients
|
|
1851
2193
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1852
2194
|
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Sends an open snackbar message to all connected clients.
|
|
2197
|
+
*
|
|
2198
|
+
* @param {string} message - The message to send.
|
|
2199
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2200
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2201
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2202
|
+
*
|
|
2203
|
+
* @remarks
|
|
2204
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2205
|
+
*/
|
|
1853
2206
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1854
2207
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1855
2208
|
return;
|
|
1856
2209
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2210
|
+
// Send the message to all connected clients
|
|
1857
2211
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1858
2212
|
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Sends a close snackbar message to all connected clients.
|
|
2215
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2216
|
+
*
|
|
2217
|
+
* @param {string} message - The message to send.
|
|
2218
|
+
*/
|
|
1859
2219
|
wssSendCloseSnackbarMessage(message) {
|
|
1860
2220
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1861
2221
|
return;
|
|
1862
2222
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2223
|
+
// Send the message to all connected clients
|
|
1863
2224
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1864
2225
|
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2228
|
+
*
|
|
2229
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2230
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2231
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2232
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2233
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2234
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2235
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2236
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2237
|
+
*
|
|
2238
|
+
* @remarks
|
|
2239
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2240
|
+
* with the updated attribute information.
|
|
2241
|
+
*/
|
|
1865
2242
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1866
2243
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1867
2244
|
return;
|
|
1868
2245
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2246
|
+
// Send the message to all connected clients
|
|
1869
2247
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1870
2248
|
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Sends a message to all connected clients.
|
|
2251
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2252
|
+
*
|
|
2253
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2254
|
+
*/
|
|
1871
2255
|
wssBroadcastMessage(msg) {
|
|
1872
2256
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1873
2257
|
return;
|
|
2258
|
+
// Send the message to all connected clients
|
|
1874
2259
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1875
2260
|
if (msg.method !== 'log')
|
|
1876
2261
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -1881,3 +2266,4 @@ export class Frontend extends EventEmitter {
|
|
|
1881
2266
|
});
|
|
1882
2267
|
}
|
|
1883
2268
|
}
|
|
2269
|
+
//# sourceMappingURL=frontend.js.map
|