matterbridge 3.3.4-dev-20251021-7651f57 → 3.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -2
- package/README-SERVICE-LOCAL.md +226 -0
- package/dist/broadcastServer.d.ts +112 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +100 -6
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +793 -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 +30 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +100 -2
- 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 +48 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +38 -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 +126 -3
- 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 +76 -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 +24 -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 +441 -39
- 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 +475 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +833 -51
- 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 +37 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +2404 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +770 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +638 -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 +37 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1550 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1403 -53
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +758 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +464 -19
- 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 +226 -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 +347 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +320 -4
- 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 +66 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +60 -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 +43 -1
- 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/format.d.ts +53 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +49 -0
- package/dist/utils/format.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/inspector.d.ts +87 -0
- package/dist/utils/inspector.d.ts.map +1 -0
- package/dist/utils/inspector.js +69 -1
- package/dist/utils/inspector.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 +139 -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 +101 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +96 -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/tracker.d.ts +108 -0
- package/dist/utils/tracker.d.ts.map +1 -0
- package/dist/utils/tracker.js +64 -1
- package/dist/utils/tracker.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 +5 -5
- 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');
|
|
27
|
+
// Node modules
|
|
3
28
|
import os from 'node:os';
|
|
4
29
|
import path from 'node:path';
|
|
5
30
|
import EventEmitter from 'node:events';
|
|
6
|
-
|
|
31
|
+
// AnsiLogger module
|
|
32
|
+
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
7
33
|
import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
|
|
8
34
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
|
|
9
35
|
import { FabricIndex } from '@matter/types/datatype';
|
|
@@ -34,7 +60,7 @@ export class Frontend extends EventEmitter {
|
|
|
34
60
|
constructor(matterbridge) {
|
|
35
61
|
super();
|
|
36
62
|
this.matterbridge = matterbridge;
|
|
37
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
63
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
38
64
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
39
65
|
this.server = new BroadcastServer('frontend', this.log);
|
|
40
66
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -44,7 +70,7 @@ export class Frontend extends EventEmitter {
|
|
|
44
70
|
}
|
|
45
71
|
async msgHandler(msg) {
|
|
46
72
|
if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'frontend')) {
|
|
47
|
-
this.log.debug(
|
|
73
|
+
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
48
74
|
switch (msg.type) {
|
|
49
75
|
case 'frontend_start':
|
|
50
76
|
await this.start(msg.params.port);
|
|
@@ -55,11 +81,11 @@ export class Frontend extends EventEmitter {
|
|
|
55
81
|
this.server.respond({ ...msg, response: { success: true } });
|
|
56
82
|
break;
|
|
57
83
|
default:
|
|
58
|
-
this.log.
|
|
84
|
+
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
59
85
|
}
|
|
60
86
|
}
|
|
61
87
|
if (this.server.isWorkerResponse(msg, msg.type)) {
|
|
62
|
-
this.log.debug(
|
|
88
|
+
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
63
89
|
switch (msg.type) {
|
|
64
90
|
case 'plugins_install':
|
|
65
91
|
this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
|
|
@@ -84,7 +110,7 @@ export class Frontend extends EventEmitter {
|
|
|
84
110
|
}
|
|
85
111
|
break;
|
|
86
112
|
default:
|
|
87
|
-
this.log.
|
|
113
|
+
this.log.debug(`Unknown broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
}
|
|
@@ -94,13 +120,42 @@ export class Frontend extends EventEmitter {
|
|
|
94
120
|
async start(port = 8283) {
|
|
95
121
|
this.port = port;
|
|
96
122
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
123
|
+
// Initialize multer with the upload directory
|
|
97
124
|
const multer = await import('multer');
|
|
98
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
125
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
99
126
|
const upload = multer.default({ dest: uploadDir });
|
|
127
|
+
// Create the express app that serves the frontend
|
|
100
128
|
const express = await import('express');
|
|
101
129
|
this.expressApp = express.default();
|
|
130
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
131
|
+
/*
|
|
132
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
133
|
+
for (const method of methods) {
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
138
|
+
try {
|
|
139
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
140
|
+
return original(path, ...rest);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
*/
|
|
148
|
+
// Log all requests to the server for debugging
|
|
149
|
+
/*
|
|
150
|
+
this.expressApp.use((req, res, next) => {
|
|
151
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
152
|
+
next();
|
|
153
|
+
});
|
|
154
|
+
*/
|
|
155
|
+
// Serve static files from 'frontend/build' directory
|
|
102
156
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
103
157
|
if (!hasParameter('ssl')) {
|
|
158
|
+
// Create an HTTP server and attach the express app
|
|
104
159
|
const http = await import('node:http');
|
|
105
160
|
try {
|
|
106
161
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -111,6 +166,7 @@ export class Frontend extends EventEmitter {
|
|
|
111
166
|
this.emit('server_error', error);
|
|
112
167
|
return;
|
|
113
168
|
}
|
|
169
|
+
// Listen on the specified port
|
|
114
170
|
if (hasParameter('ingress')) {
|
|
115
171
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
116
172
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -143,6 +199,7 @@ export class Frontend extends EventEmitter {
|
|
|
143
199
|
});
|
|
144
200
|
}
|
|
145
201
|
else {
|
|
202
|
+
// SSL is enabled, load the certificate and the private key
|
|
146
203
|
let cert;
|
|
147
204
|
let key;
|
|
148
205
|
let ca;
|
|
@@ -152,6 +209,7 @@ export class Frontend extends EventEmitter {
|
|
|
152
209
|
let httpsServerOptions = {};
|
|
153
210
|
const fs = await import('node:fs');
|
|
154
211
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
212
|
+
// Load the p12 certificate and the passphrase
|
|
155
213
|
try {
|
|
156
214
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
157
215
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -163,7 +221,7 @@ export class Frontend extends EventEmitter {
|
|
|
163
221
|
}
|
|
164
222
|
try {
|
|
165
223
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
166
|
-
passphrase = passphrase.trim();
|
|
224
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
167
225
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
168
226
|
}
|
|
169
227
|
catch (error) {
|
|
@@ -174,6 +232,7 @@ export class Frontend extends EventEmitter {
|
|
|
174
232
|
httpsServerOptions = { pfx, passphrase };
|
|
175
233
|
}
|
|
176
234
|
else {
|
|
235
|
+
// 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.
|
|
177
236
|
try {
|
|
178
237
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
179
238
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -203,9 +262,10 @@ export class Frontend extends EventEmitter {
|
|
|
203
262
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
204
263
|
}
|
|
205
264
|
if (hasParameter('mtls')) {
|
|
206
|
-
httpsServerOptions.requestCert = true;
|
|
207
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
265
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
266
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
208
267
|
}
|
|
268
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
209
269
|
const https = await import('node:https');
|
|
210
270
|
try {
|
|
211
271
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -216,6 +276,7 @@ export class Frontend extends EventEmitter {
|
|
|
216
276
|
this.emit('server_error', error);
|
|
217
277
|
return;
|
|
218
278
|
}
|
|
279
|
+
// Listen on the specified port
|
|
219
280
|
if (hasParameter('ingress')) {
|
|
220
281
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
221
282
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -247,16 +308,41 @@ export class Frontend extends EventEmitter {
|
|
|
247
308
|
return;
|
|
248
309
|
});
|
|
249
310
|
}
|
|
311
|
+
// Load the stored password
|
|
312
|
+
/*
|
|
313
|
+
let storedPassword = '';
|
|
314
|
+
try {
|
|
315
|
+
if (!this.matterbridge.nodeContext) throw new Error('nodeContext not found');
|
|
316
|
+
storedPassword = await this.matterbridge.nodeContext.get('password', '');
|
|
317
|
+
} catch (error) {
|
|
318
|
+
inspectError(this.log, 'Error getting password', error);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
*/
|
|
322
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
250
323
|
const ws = await import('ws');
|
|
251
324
|
this.log.debug(`Creating WebSocketServer...`);
|
|
252
325
|
this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
253
326
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
254
327
|
const clientIp = request.socket.remoteAddress;
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
328
|
+
/*
|
|
329
|
+
if (storedPassword !== '') {
|
|
330
|
+
// Check for the password in the query parameters
|
|
331
|
+
const url = new URL(request.url ?? '', `http://${request.headers.host}`);
|
|
332
|
+
const password = url.searchParams.get('password');
|
|
333
|
+
if (password !== storedPassword) {
|
|
334
|
+
this.log.error(`WebSocket client "${clientIp}" failed authentication: ${storedPassword}-${password}`);
|
|
335
|
+
// ws.close();
|
|
336
|
+
// return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
*/
|
|
340
|
+
// Set the global logger callback for the WebSocketServer
|
|
341
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
342
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
343
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
344
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
345
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
260
346
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
261
347
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
262
348
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -278,6 +364,7 @@ export class Frontend extends EventEmitter {
|
|
|
278
364
|
}
|
|
279
365
|
});
|
|
280
366
|
ws.on('error', (error) => {
|
|
367
|
+
// istanbul ignore next
|
|
281
368
|
this.log.error(`WebSocket client error: ${error}`);
|
|
282
369
|
});
|
|
283
370
|
});
|
|
@@ -291,6 +378,7 @@ export class Frontend extends EventEmitter {
|
|
|
291
378
|
this.webSocketServer.on('error', (ws, error) => {
|
|
292
379
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
293
380
|
});
|
|
381
|
+
// Subscribe to cli events
|
|
294
382
|
cliEmitter.removeAllListeners();
|
|
295
383
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
296
384
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -301,6 +389,8 @@ export class Frontend extends EventEmitter {
|
|
|
301
389
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
302
390
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
303
391
|
});
|
|
392
|
+
// Endpoint to validate login code
|
|
393
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
304
394
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
305
395
|
const { password } = req.body;
|
|
306
396
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -319,23 +409,27 @@ export class Frontend extends EventEmitter {
|
|
|
319
409
|
this.log.warn('/api/login error wrong password');
|
|
320
410
|
res.json({ valid: false });
|
|
321
411
|
}
|
|
412
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
322
413
|
}
|
|
323
414
|
catch (error) {
|
|
324
415
|
this.log.error('/api/login error getting password');
|
|
325
416
|
res.json({ valid: false });
|
|
326
417
|
}
|
|
327
418
|
});
|
|
419
|
+
// Endpoint to provide health check for docker
|
|
328
420
|
this.expressApp.get('/health', (req, res) => {
|
|
329
421
|
this.log.debug('Express received /health');
|
|
330
422
|
const healthStatus = {
|
|
331
|
-
status: 'ok',
|
|
332
|
-
uptime: process.uptime(),
|
|
333
|
-
timestamp: new Date().toISOString(),
|
|
423
|
+
status: 'ok', // Indicate service is healthy
|
|
424
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
425
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
334
426
|
};
|
|
335
427
|
res.status(200).json(healthStatus);
|
|
336
428
|
});
|
|
429
|
+
// Endpoint to provide memory usage details
|
|
337
430
|
this.expressApp.get('/memory', async (req, res) => {
|
|
338
431
|
this.log.debug('Express received /memory');
|
|
432
|
+
// Memory usage from process
|
|
339
433
|
const memoryUsageRaw = process.memoryUsage();
|
|
340
434
|
const memoryUsage = {
|
|
341
435
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -344,10 +438,13 @@ export class Frontend extends EventEmitter {
|
|
|
344
438
|
external: formatBytes(memoryUsageRaw.external),
|
|
345
439
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
346
440
|
};
|
|
441
|
+
// V8 heap statistics
|
|
347
442
|
const { default: v8 } = await import('node:v8');
|
|
348
443
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
349
444
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
445
|
+
// Format heapStats
|
|
350
446
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
447
|
+
// Format heapSpaces
|
|
351
448
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
352
449
|
...space,
|
|
353
450
|
space_size: formatBytes(space.space_size),
|
|
@@ -366,18 +463,22 @@ export class Frontend extends EventEmitter {
|
|
|
366
463
|
};
|
|
367
464
|
res.status(200).json(memoryReport);
|
|
368
465
|
});
|
|
466
|
+
// Endpoint to provide settings
|
|
369
467
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
370
468
|
this.log.debug('The frontend sent /api/settings');
|
|
371
469
|
res.json(await this.getApiSettings());
|
|
372
470
|
});
|
|
471
|
+
// Endpoint to provide plugins
|
|
373
472
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
374
473
|
this.log.debug('The frontend sent /api/plugins');
|
|
375
474
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
376
475
|
});
|
|
476
|
+
// Endpoint to provide devices
|
|
377
477
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
378
478
|
this.log.debug('The frontend sent /api/devices');
|
|
379
479
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
380
480
|
});
|
|
481
|
+
// Endpoint to view the matterbridge log
|
|
381
482
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
382
483
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
383
484
|
try {
|
|
@@ -391,6 +492,7 @@ export class Frontend extends EventEmitter {
|
|
|
391
492
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
392
493
|
}
|
|
393
494
|
});
|
|
495
|
+
// Endpoint to view the matter.js log
|
|
394
496
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
395
497
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
396
498
|
try {
|
|
@@ -404,6 +506,7 @@ export class Frontend extends EventEmitter {
|
|
|
404
506
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
405
507
|
}
|
|
406
508
|
});
|
|
509
|
+
// Endpoint to view the diagnostic.log
|
|
407
510
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
408
511
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
409
512
|
await this.generateDiagnostic();
|
|
@@ -414,10 +517,13 @@ export class Frontend extends EventEmitter {
|
|
|
414
517
|
res.send(data.slice(29));
|
|
415
518
|
}
|
|
416
519
|
catch (error) {
|
|
520
|
+
// istanbul ignore next
|
|
417
521
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
522
|
+
// istanbul ignore next
|
|
418
523
|
res.status(500).send('Error reading diagnostic log file.');
|
|
419
524
|
}
|
|
420
525
|
});
|
|
526
|
+
// Endpoint to download the diagnostic.log
|
|
421
527
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
422
528
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
423
529
|
await this.generateDiagnostic();
|
|
@@ -428,16 +534,19 @@ export class Frontend extends EventEmitter {
|
|
|
428
534
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
429
535
|
}
|
|
430
536
|
catch (error) {
|
|
537
|
+
// istanbul ignore next
|
|
431
538
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
432
539
|
}
|
|
433
540
|
res.type('text/plain');
|
|
434
541
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
542
|
+
/* istanbul ignore if */
|
|
435
543
|
if (error) {
|
|
436
544
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
437
545
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
438
546
|
}
|
|
439
547
|
});
|
|
440
548
|
});
|
|
549
|
+
// Endpoint to view the history.html
|
|
441
550
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
442
551
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
443
552
|
try {
|
|
@@ -451,6 +560,7 @@ export class Frontend extends EventEmitter {
|
|
|
451
560
|
res.status(500).send('Error reading history file.');
|
|
452
561
|
}
|
|
453
562
|
});
|
|
563
|
+
// Endpoint to download the history.html
|
|
454
564
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
455
565
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
456
566
|
try {
|
|
@@ -460,6 +570,7 @@ export class Frontend extends EventEmitter {
|
|
|
460
570
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
461
571
|
res.type('text/plain');
|
|
462
572
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
573
|
+
/* istanbul ignore if */
|
|
463
574
|
if (error) {
|
|
464
575
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
465
576
|
res.status(500).send('Error downloading history file');
|
|
@@ -471,6 +582,7 @@ export class Frontend extends EventEmitter {
|
|
|
471
582
|
res.status(500).send('Error reading history file.');
|
|
472
583
|
}
|
|
473
584
|
});
|
|
585
|
+
// Endpoint to view the shelly log
|
|
474
586
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
475
587
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
476
588
|
try {
|
|
@@ -484,6 +596,7 @@ export class Frontend extends EventEmitter {
|
|
|
484
596
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
485
597
|
}
|
|
486
598
|
});
|
|
599
|
+
// Endpoint to download the matterbridge log
|
|
487
600
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
488
601
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
489
602
|
const fs = await import('node:fs');
|
|
@@ -498,12 +611,14 @@ export class Frontend extends EventEmitter {
|
|
|
498
611
|
}
|
|
499
612
|
res.type('text/plain');
|
|
500
613
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
614
|
+
/* istanbul ignore if */
|
|
501
615
|
if (error) {
|
|
502
616
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
503
617
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
504
618
|
}
|
|
505
619
|
});
|
|
506
620
|
});
|
|
621
|
+
// Endpoint to download the matter log
|
|
507
622
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
508
623
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
509
624
|
const fs = await import('node:fs');
|
|
@@ -518,12 +633,14 @@ export class Frontend extends EventEmitter {
|
|
|
518
633
|
}
|
|
519
634
|
res.type('text/plain');
|
|
520
635
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
636
|
+
/* istanbul ignore if */
|
|
521
637
|
if (error) {
|
|
522
638
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
523
639
|
res.status(500).send('Error downloading the matter log file');
|
|
524
640
|
}
|
|
525
641
|
});
|
|
526
642
|
});
|
|
643
|
+
// Endpoint to download the shelly log
|
|
527
644
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
528
645
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
529
646
|
const fs = await import('node:fs');
|
|
@@ -538,75 +655,91 @@ export class Frontend extends EventEmitter {
|
|
|
538
655
|
}
|
|
539
656
|
res.type('text/plain');
|
|
540
657
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
658
|
+
/* istanbul ignore if */
|
|
541
659
|
if (error) {
|
|
542
660
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
543
661
|
res.status(500).send('Error downloading Shelly system log file');
|
|
544
662
|
}
|
|
545
663
|
});
|
|
546
664
|
});
|
|
665
|
+
// Endpoint to download the matterbridge storage directory
|
|
547
666
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
548
667
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
549
668
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
550
669
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
670
|
+
/* istanbul ignore if */
|
|
551
671
|
if (error) {
|
|
552
672
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
553
673
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
554
674
|
}
|
|
555
675
|
});
|
|
556
676
|
});
|
|
677
|
+
// Endpoint to download the matter storage file
|
|
557
678
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
558
679
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
559
680
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
560
681
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
682
|
+
/* istanbul ignore if */
|
|
561
683
|
if (error) {
|
|
562
684
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
563
685
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
564
686
|
}
|
|
565
687
|
});
|
|
566
688
|
});
|
|
689
|
+
// Endpoint to download the matterbridge plugin directory
|
|
567
690
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
568
691
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
569
692
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
570
693
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
694
|
+
/* istanbul ignore if */
|
|
571
695
|
if (error) {
|
|
572
696
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
573
697
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
574
698
|
}
|
|
575
699
|
});
|
|
576
700
|
});
|
|
701
|
+
// Endpoint to download the matterbridge plugin config files
|
|
577
702
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
578
703
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
579
704
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
580
705
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
706
|
+
/* istanbul ignore if */
|
|
581
707
|
if (error) {
|
|
582
708
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
583
709
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
584
710
|
}
|
|
585
711
|
});
|
|
586
712
|
});
|
|
713
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
587
714
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
588
715
|
this.log.debug('The frontend sent /api/download-backup');
|
|
589
716
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
717
|
+
/* istanbul ignore if */
|
|
590
718
|
if (error) {
|
|
591
719
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
592
720
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
593
721
|
}
|
|
594
722
|
});
|
|
595
723
|
});
|
|
724
|
+
// Endpoint to upload a package
|
|
596
725
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
597
726
|
const { filename } = req.body;
|
|
598
727
|
const file = req.file;
|
|
728
|
+
/* istanbul ignore if */
|
|
599
729
|
if (!file || !filename) {
|
|
600
730
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
601
731
|
res.status(400).send('Invalid request: file and filename are required');
|
|
602
732
|
return;
|
|
603
733
|
}
|
|
604
734
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
735
|
+
// Define the path where the plugin file will be saved
|
|
605
736
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
606
737
|
try {
|
|
738
|
+
// Move the uploaded file to the specified path
|
|
607
739
|
const fs = await import('node:fs');
|
|
608
740
|
await fs.promises.rename(file.path, filePath);
|
|
609
741
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
742
|
+
// Install the plugin package
|
|
610
743
|
if (filename.endsWith('.tgz')) {
|
|
611
744
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
612
745
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -626,6 +759,7 @@ export class Frontend extends EventEmitter {
|
|
|
626
759
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
627
760
|
}
|
|
628
761
|
});
|
|
762
|
+
// Fallback for routing (must be the last route)
|
|
629
763
|
this.expressApp.use((req, res) => {
|
|
630
764
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
631
765
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -635,13 +769,16 @@ export class Frontend extends EventEmitter {
|
|
|
635
769
|
async stop() {
|
|
636
770
|
this.log.debug('Stopping the frontend...');
|
|
637
771
|
const ws = await import('ws');
|
|
772
|
+
// Remove listeners from the express app
|
|
638
773
|
if (this.expressApp) {
|
|
639
774
|
this.expressApp.removeAllListeners();
|
|
640
775
|
this.expressApp = undefined;
|
|
641
776
|
this.log.debug('Frontend app closed successfully');
|
|
642
777
|
}
|
|
778
|
+
// Close the WebSocket server
|
|
643
779
|
if (this.webSocketServer) {
|
|
644
780
|
this.log.debug('Closing WebSocket server...');
|
|
781
|
+
// Close all active connections
|
|
645
782
|
this.webSocketServer.clients.forEach((client) => {
|
|
646
783
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
647
784
|
client.close();
|
|
@@ -650,6 +787,7 @@ export class Frontend extends EventEmitter {
|
|
|
650
787
|
await withTimeout(new Promise((resolve) => {
|
|
651
788
|
this.webSocketServer?.close((error) => {
|
|
652
789
|
if (error) {
|
|
790
|
+
// istanbul ignore next
|
|
653
791
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
654
792
|
}
|
|
655
793
|
else {
|
|
@@ -662,8 +800,27 @@ export class Frontend extends EventEmitter {
|
|
|
662
800
|
this.webSocketServer.removeAllListeners();
|
|
663
801
|
this.webSocketServer = undefined;
|
|
664
802
|
}
|
|
803
|
+
// Close the http server
|
|
665
804
|
if (this.httpServer) {
|
|
666
805
|
this.log.debug('Closing http server...');
|
|
806
|
+
/*
|
|
807
|
+
await withTimeout(
|
|
808
|
+
new Promise<void>((resolve) => {
|
|
809
|
+
this.httpServer?.close((error) => {
|
|
810
|
+
if (error) {
|
|
811
|
+
// istanbul ignore next
|
|
812
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
813
|
+
} else {
|
|
814
|
+
this.log.debug('Http server closed successfully');
|
|
815
|
+
this.emit('server_stopped');
|
|
816
|
+
}
|
|
817
|
+
resolve();
|
|
818
|
+
});
|
|
819
|
+
}),
|
|
820
|
+
5000,
|
|
821
|
+
false,
|
|
822
|
+
);
|
|
823
|
+
*/
|
|
667
824
|
this.httpServer.close();
|
|
668
825
|
this.log.debug('Http server closed successfully');
|
|
669
826
|
this.listening = false;
|
|
@@ -672,8 +829,27 @@ export class Frontend extends EventEmitter {
|
|
|
672
829
|
this.httpServer = undefined;
|
|
673
830
|
this.log.debug('Frontend http server closed successfully');
|
|
674
831
|
}
|
|
832
|
+
// Close the https server
|
|
675
833
|
if (this.httpsServer) {
|
|
676
834
|
this.log.debug('Closing https server...');
|
|
835
|
+
/*
|
|
836
|
+
await withTimeout(
|
|
837
|
+
new Promise<void>((resolve) => {
|
|
838
|
+
this.httpsServer?.close((error) => {
|
|
839
|
+
if (error) {
|
|
840
|
+
// istanbul ignore next
|
|
841
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
842
|
+
} else {
|
|
843
|
+
this.log.debug('Https server closed successfully');
|
|
844
|
+
this.emit('server_stopped');
|
|
845
|
+
}
|
|
846
|
+
resolve();
|
|
847
|
+
});
|
|
848
|
+
}),
|
|
849
|
+
5000,
|
|
850
|
+
false,
|
|
851
|
+
);
|
|
852
|
+
*/
|
|
677
853
|
this.httpsServer.close();
|
|
678
854
|
this.log.debug('Https server closed successfully');
|
|
679
855
|
this.listening = false;
|
|
@@ -684,7 +860,13 @@ export class Frontend extends EventEmitter {
|
|
|
684
860
|
}
|
|
685
861
|
this.log.debug('Frontend stopped successfully');
|
|
686
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Retrieves the api settings data.
|
|
865
|
+
*
|
|
866
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
867
|
+
*/
|
|
687
868
|
async getApiSettings() {
|
|
869
|
+
// Update the variable system information properties
|
|
688
870
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
689
871
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
690
872
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -694,6 +876,7 @@ export class Frontend extends EventEmitter {
|
|
|
694
876
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
695
877
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
696
878
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
879
|
+
// Create the matterbridge information
|
|
697
880
|
const info = {
|
|
698
881
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
699
882
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -729,9 +912,15 @@ export class Frontend extends EventEmitter {
|
|
|
729
912
|
};
|
|
730
913
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
731
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Retrieves the reachable attribute.
|
|
917
|
+
*
|
|
918
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
919
|
+
* @returns {boolean} The reachable attribute.
|
|
920
|
+
*/
|
|
732
921
|
getReachability(device) {
|
|
733
922
|
if (this.matterbridge.hasCleanupStarted)
|
|
734
|
-
return false;
|
|
923
|
+
return false; // Skip if cleanup has started
|
|
735
924
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
736
925
|
return false;
|
|
737
926
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -742,9 +931,15 @@ export class Frontend extends EventEmitter {
|
|
|
742
931
|
return true;
|
|
743
932
|
return false;
|
|
744
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* Retrieves the power source attribute.
|
|
936
|
+
*
|
|
937
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
938
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
939
|
+
*/
|
|
745
940
|
getPowerSource(endpoint) {
|
|
746
941
|
if (this.matterbridge.hasCleanupStarted)
|
|
747
|
-
return;
|
|
942
|
+
return; // Skip if cleanup has started
|
|
748
943
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
749
944
|
return undefined;
|
|
750
945
|
const powerSource = (device) => {
|
|
@@ -759,16 +954,25 @@ export class Frontend extends EventEmitter {
|
|
|
759
954
|
}
|
|
760
955
|
return;
|
|
761
956
|
};
|
|
957
|
+
// Root endpoint
|
|
762
958
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
763
959
|
return powerSource(endpoint);
|
|
960
|
+
// Child endpoints
|
|
764
961
|
for (const child of endpoint.getChildEndpoints()) {
|
|
765
962
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
766
963
|
return powerSource(child);
|
|
767
964
|
}
|
|
768
965
|
}
|
|
966
|
+
/**
|
|
967
|
+
* Retrieves the cluster text description from a given device.
|
|
968
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
969
|
+
*
|
|
970
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
971
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
972
|
+
*/
|
|
769
973
|
getClusterTextFromDevice(device) {
|
|
770
974
|
if (this.matterbridge.hasCleanupStarted)
|
|
771
|
-
return '';
|
|
975
|
+
return ''; // Skip if cleanup has started
|
|
772
976
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
773
977
|
return '';
|
|
774
978
|
const getUserLabel = (device) => {
|
|
@@ -778,6 +982,7 @@ export class Frontend extends EventEmitter {
|
|
|
778
982
|
if (composed)
|
|
779
983
|
return 'Composed: ' + composed.value;
|
|
780
984
|
}
|
|
985
|
+
// istanbul ignore next cause is not reachable
|
|
781
986
|
return '';
|
|
782
987
|
};
|
|
783
988
|
const getFixedLabel = (device) => {
|
|
@@ -787,11 +992,13 @@ export class Frontend extends EventEmitter {
|
|
|
787
992
|
if (composed)
|
|
788
993
|
return 'Composed: ' + composed.value;
|
|
789
994
|
}
|
|
995
|
+
// istanbul ignore next cause is not reacheable
|
|
790
996
|
return '';
|
|
791
997
|
};
|
|
792
998
|
let attributes = '';
|
|
793
999
|
let supportedModes = [];
|
|
794
1000
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1001
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
795
1002
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
796
1003
|
return;
|
|
797
1004
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -881,11 +1088,17 @@ export class Frontend extends EventEmitter {
|
|
|
881
1088
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
882
1089
|
attributes += `${getUserLabel(device)} `;
|
|
883
1090
|
});
|
|
1091
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
884
1092
|
return attributes.trimStart().trimEnd();
|
|
885
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1096
|
+
*
|
|
1097
|
+
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1098
|
+
*/
|
|
886
1099
|
getPlugins() {
|
|
887
1100
|
if (this.matterbridge.hasCleanupStarted)
|
|
888
|
-
return [];
|
|
1101
|
+
return []; // Skip if cleanup has started
|
|
889
1102
|
const plugins = [];
|
|
890
1103
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
891
1104
|
plugins.push({
|
|
@@ -913,18 +1126,27 @@ export class Frontend extends EventEmitter {
|
|
|
913
1126
|
schemaJson: plugin.schemaJson,
|
|
914
1127
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
915
1128
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1129
|
+
// Childbridge mode specific data
|
|
916
1130
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
917
1131
|
});
|
|
918
1132
|
}
|
|
919
1133
|
return plugins;
|
|
920
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Retrieves the devices from Matterbridge.
|
|
1137
|
+
*
|
|
1138
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1139
|
+
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1140
|
+
*/
|
|
921
1141
|
getDevices(pluginName) {
|
|
922
1142
|
if (this.matterbridge.hasCleanupStarted)
|
|
923
|
-
return [];
|
|
1143
|
+
return []; // Skip if cleanup has started
|
|
924
1144
|
const devices = [];
|
|
925
1145
|
for (const device of this.matterbridge.devices.array()) {
|
|
1146
|
+
// Filter by pluginName if provided
|
|
926
1147
|
if (pluginName && pluginName !== device.plugin)
|
|
927
1148
|
continue;
|
|
1149
|
+
// Check if the device has the required properties
|
|
928
1150
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
929
1151
|
continue;
|
|
930
1152
|
devices.push({
|
|
@@ -944,24 +1166,39 @@ export class Frontend extends EventEmitter {
|
|
|
944
1166
|
}
|
|
945
1167
|
return devices;
|
|
946
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1171
|
+
*
|
|
1172
|
+
* Response for /api/clusters
|
|
1173
|
+
*
|
|
1174
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1175
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1176
|
+
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1177
|
+
*/
|
|
947
1178
|
getClusters(pluginName, endpointNumber) {
|
|
948
1179
|
if (this.matterbridge.hasCleanupStarted)
|
|
949
|
-
return;
|
|
1180
|
+
return; // Skip if cleanup has started
|
|
950
1181
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
951
1182
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
952
1183
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
953
1184
|
return;
|
|
954
1185
|
}
|
|
1186
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1187
|
+
// Get the device types from the main endpoint
|
|
955
1188
|
const deviceTypes = [];
|
|
956
1189
|
const clusters = [];
|
|
957
1190
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
958
1191
|
deviceTypes.push(d.deviceType);
|
|
959
1192
|
});
|
|
1193
|
+
// Get the clusters from the main endpoint
|
|
960
1194
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
961
1195
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
962
1196
|
return;
|
|
963
1197
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
964
1198
|
return;
|
|
1199
|
+
// console.log(
|
|
1200
|
+
// `${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}`,
|
|
1201
|
+
// );
|
|
965
1202
|
clusters.push({
|
|
966
1203
|
endpoint: endpoint.number.toString(),
|
|
967
1204
|
number: endpoint.number,
|
|
@@ -975,12 +1212,19 @@ export class Frontend extends EventEmitter {
|
|
|
975
1212
|
attributeLocalValue: attributeValue,
|
|
976
1213
|
});
|
|
977
1214
|
});
|
|
1215
|
+
// Get the child endpoints
|
|
978
1216
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1217
|
+
// if (childEndpoints.length === 0) {
|
|
1218
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1219
|
+
// }
|
|
979
1220
|
childEndpoints.forEach((childEndpoint) => {
|
|
1221
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
980
1222
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
981
1223
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
982
1224
|
return;
|
|
983
1225
|
}
|
|
1226
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1227
|
+
// Get the device types of the child endpoint
|
|
984
1228
|
const deviceTypes = [];
|
|
985
1229
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
986
1230
|
deviceTypes.push(d.deviceType);
|
|
@@ -990,6 +1234,9 @@ export class Frontend extends EventEmitter {
|
|
|
990
1234
|
return;
|
|
991
1235
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
992
1236
|
return;
|
|
1237
|
+
// console.log(
|
|
1238
|
+
// `${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}`,
|
|
1239
|
+
// );
|
|
993
1240
|
clusters.push({
|
|
994
1241
|
endpoint: childEndpoint.number.toString(),
|
|
995
1242
|
number: childEndpoint.number,
|
|
@@ -1009,6 +1256,7 @@ export class Frontend extends EventEmitter {
|
|
|
1009
1256
|
async generateDiagnostic() {
|
|
1010
1257
|
this.log.debug('Generating diagnostic...');
|
|
1011
1258
|
const serverNodes = [];
|
|
1259
|
+
// istanbul ignore else
|
|
1012
1260
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1013
1261
|
if (this.matterbridge.serverNode)
|
|
1014
1262
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1019,6 +1267,7 @@ export class Frontend extends EventEmitter {
|
|
|
1019
1267
|
serverNodes.push(plugin.serverNode);
|
|
1020
1268
|
}
|
|
1021
1269
|
}
|
|
1270
|
+
// istanbul ignore next
|
|
1022
1271
|
for (const device of this.matterbridge.devices.array()) {
|
|
1023
1272
|
if (device.serverNode)
|
|
1024
1273
|
serverNodes.push(device.serverNode);
|
|
@@ -1042,8 +1291,15 @@ export class Frontend extends EventEmitter {
|
|
|
1042
1291
|
values: [...serverNodes],
|
|
1043
1292
|
})));
|
|
1044
1293
|
delete Logger.destinations.diagnostic;
|
|
1045
|
-
await wait(500);
|
|
1294
|
+
await wait(500); // Wait for the log to be written
|
|
1046
1295
|
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1298
|
+
*
|
|
1299
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1300
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1301
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1302
|
+
*/
|
|
1047
1303
|
async wsMessageHandler(client, message) {
|
|
1048
1304
|
let data;
|
|
1049
1305
|
const sendResponse = (data) => {
|
|
@@ -1063,7 +1319,7 @@ export class Frontend extends EventEmitter {
|
|
|
1063
1319
|
};
|
|
1064
1320
|
try {
|
|
1065
1321
|
data = JSON.parse(message.toString());
|
|
1066
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1322
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
1067
1323
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1068
1324
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1069
1325
|
return;
|
|
@@ -1137,6 +1393,7 @@ export class Frontend extends EventEmitter {
|
|
|
1137
1393
|
return;
|
|
1138
1394
|
})
|
|
1139
1395
|
.catch((_error) => {
|
|
1396
|
+
//
|
|
1140
1397
|
});
|
|
1141
1398
|
}
|
|
1142
1399
|
else {
|
|
@@ -1184,6 +1441,7 @@ export class Frontend extends EventEmitter {
|
|
|
1184
1441
|
return;
|
|
1185
1442
|
})
|
|
1186
1443
|
.catch((_error) => {
|
|
1444
|
+
//
|
|
1187
1445
|
});
|
|
1188
1446
|
}
|
|
1189
1447
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1209,6 +1467,7 @@ export class Frontend extends EventEmitter {
|
|
|
1209
1467
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1210
1468
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1211
1469
|
if (plugin.serverNode) {
|
|
1470
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1212
1471
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1213
1472
|
plugin.serverNode = undefined;
|
|
1214
1473
|
}
|
|
@@ -1218,18 +1477,20 @@ export class Frontend extends EventEmitter {
|
|
|
1218
1477
|
this.matterbridge.devices.remove(device);
|
|
1219
1478
|
}
|
|
1220
1479
|
}
|
|
1480
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1221
1481
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1222
1482
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1223
1483
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1224
|
-
plugin.restartRequired = false;
|
|
1484
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1225
1485
|
let needRestart = 0;
|
|
1226
1486
|
for (const plugin of this.matterbridge.plugins) {
|
|
1227
1487
|
if (plugin.restartRequired)
|
|
1228
1488
|
needRestart++;
|
|
1229
1489
|
}
|
|
1230
1490
|
if (needRestart === 0) {
|
|
1231
|
-
this.wssSendRestartNotRequired(true);
|
|
1491
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1232
1492
|
}
|
|
1493
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1233
1494
|
if (plugin.serverNode)
|
|
1234
1495
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1235
1496
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1494,22 +1755,22 @@ export class Frontend extends EventEmitter {
|
|
|
1494
1755
|
if (isValidString(data.params.value, 4)) {
|
|
1495
1756
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1496
1757
|
if (data.params.value === 'Debug') {
|
|
1497
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1758
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1498
1759
|
}
|
|
1499
1760
|
else if (data.params.value === 'Info') {
|
|
1500
|
-
await this.matterbridge.setLogLevel("info");
|
|
1761
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1501
1762
|
}
|
|
1502
1763
|
else if (data.params.value === 'Notice') {
|
|
1503
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1764
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1504
1765
|
}
|
|
1505
1766
|
else if (data.params.value === 'Warn') {
|
|
1506
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1767
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1507
1768
|
}
|
|
1508
1769
|
else if (data.params.value === 'Error') {
|
|
1509
|
-
await this.matterbridge.setLogLevel("error");
|
|
1770
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1510
1771
|
}
|
|
1511
1772
|
else if (data.params.value === 'Fatal') {
|
|
1512
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1773
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1513
1774
|
}
|
|
1514
1775
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1515
1776
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1520,6 +1781,7 @@ export class Frontend extends EventEmitter {
|
|
|
1520
1781
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1521
1782
|
this.matterbridge.fileLogger = data.params.value;
|
|
1522
1783
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1784
|
+
// Create the file logger for matterbridge
|
|
1523
1785
|
if (data.params.value)
|
|
1524
1786
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1525
1787
|
else
|
|
@@ -1548,11 +1810,12 @@ export class Frontend extends EventEmitter {
|
|
|
1548
1810
|
else if (data.params.value === 'Fatal') {
|
|
1549
1811
|
Logger.level = MatterLogLevel.FATAL;
|
|
1550
1812
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1813
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1814
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1815
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1816
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1817
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1818
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1556
1819
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1557
1820
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1558
1821
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1604,6 +1867,7 @@ export class Frontend extends EventEmitter {
|
|
|
1604
1867
|
}
|
|
1605
1868
|
break;
|
|
1606
1869
|
case 'setmatterport':
|
|
1870
|
+
// eslint-disable-next-line no-case-declarations
|
|
1607
1871
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1608
1872
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1609
1873
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1623,6 +1887,7 @@ export class Frontend extends EventEmitter {
|
|
|
1623
1887
|
}
|
|
1624
1888
|
break;
|
|
1625
1889
|
case 'setmatterdiscriminator':
|
|
1890
|
+
// eslint-disable-next-line no-case-declarations
|
|
1626
1891
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1627
1892
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1628
1893
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1642,6 +1907,7 @@ export class Frontend extends EventEmitter {
|
|
|
1642
1907
|
}
|
|
1643
1908
|
break;
|
|
1644
1909
|
case 'setmatterpasscode':
|
|
1910
|
+
// eslint-disable-next-line no-case-declarations
|
|
1645
1911
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1646
1912
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1647
1913
|
this.matterbridge.passcode = passcode;
|
|
@@ -1687,15 +1953,19 @@ export class Frontend extends EventEmitter {
|
|
|
1687
1953
|
return;
|
|
1688
1954
|
}
|
|
1689
1955
|
const config = plugin.configJson;
|
|
1956
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1690
1957
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1958
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1691
1959
|
if (select === 'serial')
|
|
1692
1960
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1693
1961
|
if (select === 'name')
|
|
1694
1962
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1695
1963
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1964
|
+
// Remove postfix from the serial if it exists
|
|
1696
1965
|
if (config.postfix) {
|
|
1697
1966
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1698
1967
|
}
|
|
1968
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1699
1969
|
if (isValidArray(config.whiteList, 1)) {
|
|
1700
1970
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1701
1971
|
config.whiteList.push(data.params.serial);
|
|
@@ -1704,6 +1974,7 @@ export class Frontend extends EventEmitter {
|
|
|
1704
1974
|
config.whiteList.push(data.params.name);
|
|
1705
1975
|
}
|
|
1706
1976
|
}
|
|
1977
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1707
1978
|
if (isValidArray(config.blackList, 1)) {
|
|
1708
1979
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1709
1980
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1731,7 +2002,9 @@ export class Frontend extends EventEmitter {
|
|
|
1731
2002
|
return;
|
|
1732
2003
|
}
|
|
1733
2004
|
const config = plugin.configJson;
|
|
2005
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1734
2006
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2007
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1735
2008
|
if (select === 'serial')
|
|
1736
2009
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1737
2010
|
if (select === 'name')
|
|
@@ -1740,6 +2013,7 @@ export class Frontend extends EventEmitter {
|
|
|
1740
2013
|
if (config.postfix) {
|
|
1741
2014
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1742
2015
|
}
|
|
2016
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1743
2017
|
if (isValidArray(config.whiteList, 1)) {
|
|
1744
2018
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1745
2019
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1748,6 +2022,7 @@ export class Frontend extends EventEmitter {
|
|
|
1748
2022
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1749
2023
|
}
|
|
1750
2024
|
}
|
|
2025
|
+
// Add the serial to the blackList
|
|
1751
2026
|
if (isValidArray(config.blackList)) {
|
|
1752
2027
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1753
2028
|
config.blackList.push(data.params.serial);
|
|
@@ -1770,6 +2045,7 @@ export class Frontend extends EventEmitter {
|
|
|
1770
2045
|
}
|
|
1771
2046
|
}
|
|
1772
2047
|
else {
|
|
2048
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1773
2049
|
const localData = data;
|
|
1774
2050
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1775
2051
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1779,23 +2055,46 @@ export class Frontend extends EventEmitter {
|
|
|
1779
2055
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1780
2056
|
}
|
|
1781
2057
|
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2060
|
+
*
|
|
2061
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2062
|
+
* @param {string} time - The time string of the message
|
|
2063
|
+
* @param {string} name - The logger name of the message
|
|
2064
|
+
* @param {string} message - The content of the message.
|
|
2065
|
+
*
|
|
2066
|
+
* @remarks
|
|
2067
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2068
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2069
|
+
* The function sends the message to all connected clients.
|
|
2070
|
+
*/
|
|
1782
2071
|
wssSendLogMessage(level, time, name, message) {
|
|
1783
2072
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1784
2073
|
return;
|
|
1785
2074
|
if (!level || !time || !name || !message)
|
|
1786
2075
|
return;
|
|
2076
|
+
// Remove ANSI escape codes from the message
|
|
2077
|
+
// eslint-disable-next-line no-control-regex
|
|
1787
2078
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2079
|
+
// Remove leading asterisks from the message
|
|
1788
2080
|
message = message.replace(/^\*+/, '');
|
|
2081
|
+
// Replace all occurrences of \t and \n
|
|
1789
2082
|
message = message.replace(/[\t\n]/g, '');
|
|
2083
|
+
// Remove non-printable characters
|
|
2084
|
+
// eslint-disable-next-line no-control-regex
|
|
1790
2085
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2086
|
+
// Replace all occurrences of \" with "
|
|
1791
2087
|
message = message.replace(/\\"/g, '"');
|
|
2088
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1792
2089
|
const maxContinuousLength = 100;
|
|
1793
2090
|
const keepStartLength = 20;
|
|
1794
2091
|
const keepEndLength = 20;
|
|
2092
|
+
// Split the message into words
|
|
1795
2093
|
if (level !== 'spawn') {
|
|
1796
2094
|
message = message
|
|
1797
2095
|
.split(' ')
|
|
1798
2096
|
.map((word) => {
|
|
2097
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1799
2098
|
if (word.length > maxContinuousLength) {
|
|
1800
2099
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1801
2100
|
}
|
|
@@ -1803,14 +2102,34 @@ export class Frontend extends EventEmitter {
|
|
|
1803
2102
|
})
|
|
1804
2103
|
.join(' ');
|
|
1805
2104
|
}
|
|
2105
|
+
// Send the message to all connected clients
|
|
1806
2106
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1807
2107
|
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2110
|
+
*
|
|
2111
|
+
* @param {string} changed - The changed value.
|
|
2112
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2113
|
+
* possible values for changed:
|
|
2114
|
+
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2115
|
+
* - 'plugins'
|
|
2116
|
+
* - 'devices'
|
|
2117
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2118
|
+
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2119
|
+
*/
|
|
1808
2120
|
wssSendRefreshRequired(changed, params) {
|
|
1809
2121
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1810
2122
|
return;
|
|
1811
2123
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2124
|
+
// Send the message to all connected clients
|
|
1812
2125
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1813
2126
|
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2129
|
+
*
|
|
2130
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2131
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2132
|
+
*/
|
|
1814
2133
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1815
2134
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1816
2135
|
return;
|
|
@@ -1819,8 +2138,14 @@ export class Frontend extends EventEmitter {
|
|
|
1819
2138
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
1820
2139
|
if (snackbar === true)
|
|
1821
2140
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2141
|
+
// Send the message to all connected clients
|
|
1822
2142
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1823
2143
|
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2146
|
+
*
|
|
2147
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2148
|
+
*/
|
|
1824
2149
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1825
2150
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1826
2151
|
return;
|
|
@@ -1828,57 +2153,133 @@ export class Frontend extends EventEmitter {
|
|
|
1828
2153
|
this.matterbridge.restartRequired = false;
|
|
1829
2154
|
if (snackbar === true)
|
|
1830
2155
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2156
|
+
// Send the message to all connected clients
|
|
1831
2157
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1832
2158
|
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2161
|
+
*
|
|
2162
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2163
|
+
*/
|
|
1833
2164
|
wssSendUpdateRequired(devVersion = false) {
|
|
1834
2165
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1835
2166
|
return;
|
|
1836
2167
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1837
2168
|
this.matterbridge.updateRequired = true;
|
|
2169
|
+
// Send the message to all connected clients
|
|
1838
2170
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1839
2171
|
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Sends a cpu update message to all connected clients.
|
|
2174
|
+
*
|
|
2175
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2176
|
+
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2177
|
+
*/
|
|
1840
2178
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
1841
2179
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1842
2180
|
return;
|
|
1843
2181
|
if (hasParameter('debug'))
|
|
1844
2182
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2183
|
+
// Send the message to all connected clients
|
|
1845
2184
|
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 } });
|
|
1846
2185
|
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Sends a memory update message to all connected clients.
|
|
2188
|
+
*
|
|
2189
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2190
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2191
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2192
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2193
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2194
|
+
* @param {string} external - The external memory in bytes.
|
|
2195
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2196
|
+
*/
|
|
1847
2197
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1848
2198
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1849
2199
|
return;
|
|
1850
2200
|
if (hasParameter('debug'))
|
|
1851
2201
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2202
|
+
// Send the message to all connected clients
|
|
1852
2203
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1853
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Sends an uptime update message to all connected clients.
|
|
2207
|
+
*
|
|
2208
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2209
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2210
|
+
*/
|
|
1854
2211
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1855
2212
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1856
2213
|
return;
|
|
1857
2214
|
if (hasParameter('debug'))
|
|
1858
2215
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2216
|
+
// Send the message to all connected clients
|
|
1859
2217
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1860
2218
|
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Sends an open snackbar message to all connected clients.
|
|
2221
|
+
*
|
|
2222
|
+
* @param {string} message - The message to send.
|
|
2223
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2224
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2225
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2226
|
+
*
|
|
2227
|
+
* @remarks
|
|
2228
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2229
|
+
*/
|
|
1861
2230
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1862
2231
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1863
2232
|
return;
|
|
1864
2233
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2234
|
+
// Send the message to all connected clients
|
|
1865
2235
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1866
2236
|
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Sends a close snackbar message to all connected clients.
|
|
2239
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2240
|
+
*
|
|
2241
|
+
* @param {string} message - The message to send.
|
|
2242
|
+
*/
|
|
1867
2243
|
wssSendCloseSnackbarMessage(message) {
|
|
1868
2244
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1869
2245
|
return;
|
|
1870
2246
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2247
|
+
// Send the message to all connected clients
|
|
1871
2248
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1872
2249
|
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2252
|
+
*
|
|
2253
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2254
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2255
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2256
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2257
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2258
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2259
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2260
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2261
|
+
*
|
|
2262
|
+
* @remarks
|
|
2263
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2264
|
+
* with the updated attribute information.
|
|
2265
|
+
*/
|
|
1873
2266
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1874
2267
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1875
2268
|
return;
|
|
1876
2269
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2270
|
+
// Send the message to all connected clients
|
|
1877
2271
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1878
2272
|
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Sends a message to all connected clients.
|
|
2275
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2276
|
+
*
|
|
2277
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2278
|
+
*/
|
|
1879
2279
|
wssBroadcastMessage(msg) {
|
|
1880
2280
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1881
2281
|
return;
|
|
2282
|
+
// Send the message to all connected clients
|
|
1882
2283
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1883
2284
|
if (msg.method !== 'log')
|
|
1884
2285
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -1889,3 +2290,4 @@ export class Frontend extends EventEmitter {
|
|
|
1889
2290
|
});
|
|
1890
2291
|
}
|
|
1891
2292
|
}
|
|
2293
|
+
//# sourceMappingURL=frontend.js.map
|