matterbridge 3.3.4 → 3.3.5-dev-20251028-d89f93f
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 +14 -0
- package/dist/broadcastServer.js +1 -92
- package/dist/broadcastServerTypes.js +0 -24
- package/dist/cli.js +1 -97
- package/dist/cliEmitter.js +0 -37
- package/dist/cliHistory.js +0 -38
- package/dist/clusters/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -24
- package/dist/deviceManager.js +8 -124
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.js +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +0 -42
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/coap.js +13 -126
- package/dist/dgram/dgram.js +2 -114
- package/dist/dgram/mb_coap.js +3 -41
- package/dist/dgram/mb_mdns.js +15 -80
- package/dist/dgram/mdns.js +137 -299
- package/dist/dgram/multicast.js +1 -62
- package/dist/dgram/unicast.js +0 -54
- package/dist/frontend.js +165 -493
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +0 -53
- package/dist/index.js +0 -25
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -3
- package/dist/matter/types.js +0 -3
- package/dist/matterbridge.js +53 -828
- package/dist/matterbridgeAccessoryPlatform.js +0 -37
- package/dist/matterbridgeBehaviors.js +5 -68
- package/dist/matterbridgeDeviceTypes.js +17 -638
- package/dist/matterbridgeDynamicPlatform.js +0 -37
- package/dist/matterbridgeEndpoint.js +52 -1402
- package/dist/matterbridgeEndpointHelpers.js +19 -464
- package/dist/matterbridgePlatform.js +1 -341
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +11 -319
- package/dist/shelly.js +7 -168
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -69
- package/dist/utils/colorUtils.js +2 -97
- package/dist/utils/commandLine.js +0 -60
- package/dist/utils/copyDirectory.js +1 -38
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/error.js +0 -41
- package/dist/utils/export.js +0 -1
- package/dist/utils/format.js +0 -49
- package/dist/utils/hex.js +0 -124
- package/dist/utils/inspector.js +1 -69
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/jestHelpers.js +3 -153
- package/dist/utils/network.js +5 -96
- package/dist/utils/spawn.js +0 -71
- package/dist/utils/tracker.js +1 -64
- package/dist/utils/wait.js +8 -60
- package/frontend/build/assets/index.js +4 -4
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -2
- package/dist/broadcastServer.d.ts +0 -112
- package/dist/broadcastServer.d.ts.map +0 -1
- package/dist/broadcastServer.js.map +0 -1
- package/dist/broadcastServerTypes.d.ts +0 -793
- package/dist/broadcastServerTypes.d.ts.map +0 -1
- package/dist/broadcastServerTypes.js.map +0 -1
- package/dist/cli.d.ts +0 -30
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts +0 -50
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/cliHistory.d.ts +0 -48
- package/dist/cliHistory.d.ts.map +0 -1
- package/dist/cliHistory.js.map +0 -1
- package/dist/clusters/export.d.ts +0 -2
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -28
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -117
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts +0 -98
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts +0 -48
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/cooktop.d.ts +0 -60
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts +0 -71
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts +0 -76
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts +0 -17
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts +0 -46
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts +0 -47
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts +0 -67
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts +0 -81
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts +0 -168
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts +0 -105
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts +0 -118
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts +0 -40
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts +0 -87
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts +0 -166
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts +0 -111
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/coap.d.ts +0 -205
- package/dist/dgram/coap.d.ts.map +0 -1
- package/dist/dgram/coap.js.map +0 -1
- package/dist/dgram/dgram.d.ts +0 -141
- package/dist/dgram/dgram.d.ts.map +0 -1
- package/dist/dgram/dgram.js.map +0 -1
- package/dist/dgram/mb_coap.d.ts +0 -24
- package/dist/dgram/mb_coap.d.ts.map +0 -1
- package/dist/dgram/mb_coap.js.map +0 -1
- package/dist/dgram/mb_mdns.d.ts +0 -24
- package/dist/dgram/mb_mdns.d.ts.map +0 -1
- package/dist/dgram/mb_mdns.js.map +0 -1
- package/dist/dgram/mdns.d.ts +0 -290
- package/dist/dgram/mdns.d.ts.map +0 -1
- package/dist/dgram/mdns.js.map +0 -1
- package/dist/dgram/multicast.d.ts +0 -67
- package/dist/dgram/multicast.d.ts.map +0 -1
- package/dist/dgram/multicast.js.map +0 -1
- package/dist/dgram/unicast.d.ts +0 -56
- package/dist/dgram/unicast.d.ts.map +0 -1
- package/dist/dgram/unicast.js.map +0 -1
- package/dist/frontend.d.ts +0 -235
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts +0 -529
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/helpers.d.ts +0 -48
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -33
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -475
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -2404
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -770
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1550
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -758
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -402
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -226
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -347
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts +0 -174
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -75
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -99
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -66
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -33
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createDirectory.d.ts +0 -34
- package/dist/utils/createDirectory.d.ts.map +0 -1
- package/dist/utils/createDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -39
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -32
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -54
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/error.d.ts +0 -44
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/error.js.map +0 -1
- package/dist/utils/export.d.ts +0 -13
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/format.d.ts +0 -53
- package/dist/utils/format.d.ts.map +0 -1
- package/dist/utils/format.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -89
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/inspector.d.ts +0 -87
- package/dist/utils/inspector.d.ts.map +0 -1
- package/dist/utils/inspector.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -103
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/jestHelpers.d.ts +0 -139
- package/dist/utils/jestHelpers.d.ts.map +0 -1
- package/dist/utils/jestHelpers.js.map +0 -1
- package/dist/utils/network.d.ts +0 -101
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -35
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/tracker.d.ts +0 -108
- package/dist/utils/tracker.d.ts.map +0 -1
- package/dist/utils/tracker.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -54
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js
CHANGED
|
@@ -1,34 +1,8 @@
|
|
|
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
|
|
25
1
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
26
2
|
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
27
|
-
// Node modules
|
|
28
3
|
import os from 'node:os';
|
|
29
4
|
import path from 'node:path';
|
|
30
5
|
import EventEmitter from 'node:events';
|
|
31
|
-
// AnsiLogger module
|
|
32
6
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
33
7
|
import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
|
|
34
8
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
|
|
@@ -52,6 +26,7 @@ export class Frontend extends EventEmitter {
|
|
|
52
26
|
log;
|
|
53
27
|
port = 8283;
|
|
54
28
|
listening = false;
|
|
29
|
+
storedPassword = undefined;
|
|
55
30
|
expressApp;
|
|
56
31
|
httpServer;
|
|
57
32
|
httpsServer;
|
|
@@ -60,7 +35,7 @@ export class Frontend extends EventEmitter {
|
|
|
60
35
|
constructor(matterbridge) {
|
|
61
36
|
super();
|
|
62
37
|
this.matterbridge = matterbridge;
|
|
63
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
38
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
64
39
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
65
40
|
this.server = new BroadcastServer('frontend', this.log);
|
|
66
41
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -72,6 +47,13 @@ export class Frontend extends EventEmitter {
|
|
|
72
47
|
if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'frontend')) {
|
|
73
48
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
74
49
|
switch (msg.type) {
|
|
50
|
+
case 'get_log_level':
|
|
51
|
+
this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
|
|
52
|
+
break;
|
|
53
|
+
case 'set_log_level':
|
|
54
|
+
this.log.logLevel = msg.params.logLevel;
|
|
55
|
+
this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
|
|
56
|
+
break;
|
|
75
57
|
case 'frontend_start':
|
|
76
58
|
await this.start(msg.params.port);
|
|
77
59
|
this.server.respond({ ...msg, response: { success: true } });
|
|
@@ -80,6 +62,30 @@ export class Frontend extends EventEmitter {
|
|
|
80
62
|
await this.stop();
|
|
81
63
|
this.server.respond({ ...msg, response: { success: true } });
|
|
82
64
|
break;
|
|
65
|
+
case 'frontend_refreshrequired':
|
|
66
|
+
this.wssSendRefreshRequired(msg.params.changed, { matter: msg.params.matter });
|
|
67
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
68
|
+
break;
|
|
69
|
+
case 'frontend_restartrequired':
|
|
70
|
+
this.wssSendRestartRequired(msg.params.snackbar, msg.params.fixed);
|
|
71
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
72
|
+
break;
|
|
73
|
+
case 'frontend_restartnotrequired':
|
|
74
|
+
this.wssSendRestartNotRequired(msg.params.snackbar);
|
|
75
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
76
|
+
break;
|
|
77
|
+
case 'frontend_updaterequired':
|
|
78
|
+
this.wssSendUpdateRequired(msg.params.devVersion);
|
|
79
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
80
|
+
break;
|
|
81
|
+
case 'frontend_snackbarmessage':
|
|
82
|
+
this.wssSendSnackbarMessage(msg.params.message, msg.params.timeout, msg.params.severity);
|
|
83
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
84
|
+
break;
|
|
85
|
+
case 'frontend_attributechanged':
|
|
86
|
+
this.wssSendAttributeChangedMessage(msg.params.plugin, msg.params.serialNumber, msg.params.uniqueId, msg.params.number, msg.params.id, msg.params.cluster, msg.params.attribute, msg.params.value);
|
|
87
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
88
|
+
break;
|
|
83
89
|
default:
|
|
84
90
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
85
91
|
}
|
|
@@ -87,6 +93,9 @@ export class Frontend extends EventEmitter {
|
|
|
87
93
|
if (this.server.isWorkerResponse(msg, msg.type)) {
|
|
88
94
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
89
95
|
switch (msg.type) {
|
|
96
|
+
case 'get_log_level':
|
|
97
|
+
case 'set_log_level':
|
|
98
|
+
break;
|
|
90
99
|
case 'plugins_install':
|
|
91
100
|
this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
|
|
92
101
|
if (msg.response.success) {
|
|
@@ -119,43 +128,56 @@ export class Frontend extends EventEmitter {
|
|
|
119
128
|
}
|
|
120
129
|
async start(port = 8283) {
|
|
121
130
|
this.port = port;
|
|
131
|
+
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
122
132
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
123
|
-
// Initialize multer with the upload directory
|
|
124
133
|
const multer = await import('multer');
|
|
125
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
134
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
126
135
|
const upload = multer.default({ dest: uploadDir });
|
|
127
|
-
// Create the express app that serves the frontend
|
|
128
136
|
const express = await import('express');
|
|
129
137
|
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
|
|
156
138
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
139
|
+
this.log.debug(`Creating WebSocketServer...`);
|
|
140
|
+
const ws = await import('ws');
|
|
141
|
+
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
142
|
+
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
143
|
+
this.webSocketServer.on('connection', (ws, request) => {
|
|
144
|
+
const clientIp = request.socket.remoteAddress;
|
|
145
|
+
let callbackLogLevel = "notice";
|
|
146
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
147
|
+
callbackLogLevel = "info";
|
|
148
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
149
|
+
callbackLogLevel = "debug";
|
|
150
|
+
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
151
|
+
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
152
|
+
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
153
|
+
ws.on('message', (message) => {
|
|
154
|
+
this.wsMessageHandler(ws, message);
|
|
155
|
+
});
|
|
156
|
+
ws.on('ping', () => {
|
|
157
|
+
this.log.debug('WebSocket client ping');
|
|
158
|
+
ws.pong();
|
|
159
|
+
});
|
|
160
|
+
ws.on('pong', () => {
|
|
161
|
+
this.log.debug('WebSocket client pong');
|
|
162
|
+
});
|
|
163
|
+
ws.on('close', () => {
|
|
164
|
+
this.log.info('WebSocket client disconnected');
|
|
165
|
+
if (this.webSocketServer?.clients.size === 0) {
|
|
166
|
+
AnsiLogger.setGlobalCallback(undefined);
|
|
167
|
+
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
ws.on('error', (error) => {
|
|
171
|
+
this.log.error(`WebSocket client error: ${error}`);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
this.webSocketServer.on('close', () => {
|
|
175
|
+
this.log.debug(`WebSocketServer closed`);
|
|
176
|
+
});
|
|
177
|
+
this.webSocketServer.on('error', (ws, error) => {
|
|
178
|
+
this.log.error(`WebSocketServer error: ${error}`);
|
|
179
|
+
});
|
|
157
180
|
if (!hasParameter('ssl')) {
|
|
158
|
-
// Create an HTTP server and attach the express app
|
|
159
181
|
const http = await import('node:http');
|
|
160
182
|
try {
|
|
161
183
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -166,7 +188,6 @@ export class Frontend extends EventEmitter {
|
|
|
166
188
|
this.emit('server_error', error);
|
|
167
189
|
return;
|
|
168
190
|
}
|
|
169
|
-
// Listen on the specified port
|
|
170
191
|
if (hasParameter('ingress')) {
|
|
171
192
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
172
193
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -184,6 +205,32 @@ export class Frontend extends EventEmitter {
|
|
|
184
205
|
this.emit('server_listening', 'http', this.port);
|
|
185
206
|
});
|
|
186
207
|
}
|
|
208
|
+
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
209
|
+
try {
|
|
210
|
+
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
211
|
+
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
212
|
+
return socket.destroy();
|
|
213
|
+
}
|
|
214
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
215
|
+
const password = url.searchParams.get('password') ?? '';
|
|
216
|
+
if (password !== this.storedPassword) {
|
|
217
|
+
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
218
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
219
|
+
return socket.destroy();
|
|
220
|
+
}
|
|
221
|
+
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
222
|
+
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
223
|
+
this.webSocketServer?.emit('connection', ws, req);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
{
|
|
228
|
+
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
229
|
+
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
230
|
+
socket.destroy();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
187
234
|
this.httpServer.on('error', (error) => {
|
|
188
235
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
189
236
|
switch (error.code) {
|
|
@@ -199,7 +246,6 @@ export class Frontend extends EventEmitter {
|
|
|
199
246
|
});
|
|
200
247
|
}
|
|
201
248
|
else {
|
|
202
|
-
// SSL is enabled, load the certificate and the private key
|
|
203
249
|
let cert;
|
|
204
250
|
let key;
|
|
205
251
|
let ca;
|
|
@@ -209,7 +255,6 @@ export class Frontend extends EventEmitter {
|
|
|
209
255
|
let httpsServerOptions = {};
|
|
210
256
|
const fs = await import('node:fs');
|
|
211
257
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
212
|
-
// Load the p12 certificate and the passphrase
|
|
213
258
|
try {
|
|
214
259
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
215
260
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -221,7 +266,7 @@ export class Frontend extends EventEmitter {
|
|
|
221
266
|
}
|
|
222
267
|
try {
|
|
223
268
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
224
|
-
passphrase = passphrase.trim();
|
|
269
|
+
passphrase = passphrase.trim();
|
|
225
270
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
226
271
|
}
|
|
227
272
|
catch (error) {
|
|
@@ -232,7 +277,6 @@ export class Frontend extends EventEmitter {
|
|
|
232
277
|
httpsServerOptions = { pfx, passphrase };
|
|
233
278
|
}
|
|
234
279
|
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.
|
|
236
280
|
try {
|
|
237
281
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
238
282
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -262,10 +306,9 @@ export class Frontend extends EventEmitter {
|
|
|
262
306
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
263
307
|
}
|
|
264
308
|
if (hasParameter('mtls')) {
|
|
265
|
-
httpsServerOptions.requestCert = true;
|
|
266
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
309
|
+
httpsServerOptions.requestCert = true;
|
|
310
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
267
311
|
}
|
|
268
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
269
312
|
const https = await import('node:https');
|
|
270
313
|
try {
|
|
271
314
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -276,7 +319,6 @@ export class Frontend extends EventEmitter {
|
|
|
276
319
|
this.emit('server_error', error);
|
|
277
320
|
return;
|
|
278
321
|
}
|
|
279
|
-
// Listen on the specified port
|
|
280
322
|
if (hasParameter('ingress')) {
|
|
281
323
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
282
324
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -294,6 +336,32 @@ export class Frontend extends EventEmitter {
|
|
|
294
336
|
this.emit('server_listening', 'https', this.port);
|
|
295
337
|
});
|
|
296
338
|
}
|
|
339
|
+
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
340
|
+
try {
|
|
341
|
+
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
342
|
+
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
343
|
+
return socket.destroy();
|
|
344
|
+
}
|
|
345
|
+
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
346
|
+
const password = url.searchParams.get('password') ?? '';
|
|
347
|
+
if (password !== this.storedPassword) {
|
|
348
|
+
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
349
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
350
|
+
return socket.destroy();
|
|
351
|
+
}
|
|
352
|
+
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
353
|
+
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
354
|
+
this.webSocketServer?.emit('connection', ws, req);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
{
|
|
359
|
+
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
360
|
+
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
361
|
+
socket.destroy();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
297
365
|
this.httpsServer.on('error', (error) => {
|
|
298
366
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
299
367
|
switch (error.code) {
|
|
@@ -308,77 +376,6 @@ export class Frontend extends EventEmitter {
|
|
|
308
376
|
return;
|
|
309
377
|
});
|
|
310
378
|
}
|
|
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
|
|
323
|
-
const ws = await import('ws');
|
|
324
|
-
this.log.debug(`Creating WebSocketServer...`);
|
|
325
|
-
this.webSocketServer = new ws.WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
326
|
-
this.webSocketServer.on('connection', (ws, request) => {
|
|
327
|
-
const clientIp = request.socket.remoteAddress;
|
|
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 */;
|
|
346
|
-
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
347
|
-
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
348
|
-
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
349
|
-
ws.on('message', (message) => {
|
|
350
|
-
this.wsMessageHandler(ws, message);
|
|
351
|
-
});
|
|
352
|
-
ws.on('ping', () => {
|
|
353
|
-
this.log.debug('WebSocket client ping');
|
|
354
|
-
ws.pong();
|
|
355
|
-
});
|
|
356
|
-
ws.on('pong', () => {
|
|
357
|
-
this.log.debug('WebSocket client pong');
|
|
358
|
-
});
|
|
359
|
-
ws.on('close', () => {
|
|
360
|
-
this.log.info('WebSocket client disconnected');
|
|
361
|
-
if (this.webSocketServer?.clients.size === 0) {
|
|
362
|
-
AnsiLogger.setGlobalCallback(undefined);
|
|
363
|
-
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
ws.on('error', (error) => {
|
|
367
|
-
// istanbul ignore next
|
|
368
|
-
this.log.error(`WebSocket client error: ${error}`);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
this.webSocketServer.on('close', () => {
|
|
372
|
-
this.log.debug(`WebSocketServer closed`);
|
|
373
|
-
});
|
|
374
|
-
this.webSocketServer.on('listening', () => {
|
|
375
|
-
this.log.info(`The WebSocketServer is listening`);
|
|
376
|
-
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
377
|
-
});
|
|
378
|
-
this.webSocketServer.on('error', (ws, error) => {
|
|
379
|
-
this.log.error(`WebSocketServer error: ${error}`);
|
|
380
|
-
});
|
|
381
|
-
// Subscribe to cli events
|
|
382
379
|
cliEmitter.removeAllListeners();
|
|
383
380
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
384
381
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -389,47 +386,29 @@ export class Frontend extends EventEmitter {
|
|
|
389
386
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
390
387
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
391
388
|
});
|
|
392
|
-
// Endpoint to validate login code
|
|
393
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
394
389
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
395
390
|
const { password } = req.body;
|
|
396
|
-
this.log.debug(
|
|
397
|
-
if (
|
|
398
|
-
this.log.
|
|
399
|
-
res.json({ valid:
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
try {
|
|
403
|
-
const storedPassword = await this.matterbridge.nodeContext.get('password', '');
|
|
404
|
-
if (storedPassword === '' || password === storedPassword) {
|
|
405
|
-
this.log.debug('/api/login password valid');
|
|
406
|
-
res.json({ valid: true });
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
this.log.warn('/api/login error wrong password');
|
|
410
|
-
res.json({ valid: false });
|
|
411
|
-
}
|
|
412
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
391
|
+
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
392
|
+
if (this.storedPassword === '' || password === this.storedPassword) {
|
|
393
|
+
this.log.debug('/api/login password valid');
|
|
394
|
+
res.json({ valid: true });
|
|
413
395
|
}
|
|
414
|
-
|
|
415
|
-
this.log.
|
|
396
|
+
else {
|
|
397
|
+
this.log.warn('/api/login error wrong password');
|
|
416
398
|
res.json({ valid: false });
|
|
417
399
|
}
|
|
418
400
|
});
|
|
419
|
-
// Endpoint to provide health check for docker
|
|
420
401
|
this.expressApp.get('/health', (req, res) => {
|
|
421
402
|
this.log.debug('Express received /health');
|
|
422
403
|
const healthStatus = {
|
|
423
|
-
status: 'ok',
|
|
424
|
-
uptime: process.uptime(),
|
|
425
|
-
timestamp: new Date().toISOString(),
|
|
404
|
+
status: 'ok',
|
|
405
|
+
uptime: process.uptime(),
|
|
406
|
+
timestamp: new Date().toISOString(),
|
|
426
407
|
};
|
|
427
408
|
res.status(200).json(healthStatus);
|
|
428
409
|
});
|
|
429
|
-
// Endpoint to provide memory usage details
|
|
430
410
|
this.expressApp.get('/memory', async (req, res) => {
|
|
431
411
|
this.log.debug('Express received /memory');
|
|
432
|
-
// Memory usage from process
|
|
433
412
|
const memoryUsageRaw = process.memoryUsage();
|
|
434
413
|
const memoryUsage = {
|
|
435
414
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -438,13 +417,10 @@ export class Frontend extends EventEmitter {
|
|
|
438
417
|
external: formatBytes(memoryUsageRaw.external),
|
|
439
418
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
440
419
|
};
|
|
441
|
-
// V8 heap statistics
|
|
442
420
|
const { default: v8 } = await import('node:v8');
|
|
443
421
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
444
422
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
445
|
-
// Format heapStats
|
|
446
423
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
447
|
-
// Format heapSpaces
|
|
448
424
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
449
425
|
...space,
|
|
450
426
|
space_size: formatBytes(space.space_size),
|
|
@@ -463,22 +439,18 @@ export class Frontend extends EventEmitter {
|
|
|
463
439
|
};
|
|
464
440
|
res.status(200).json(memoryReport);
|
|
465
441
|
});
|
|
466
|
-
// Endpoint to provide settings
|
|
467
442
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
468
443
|
this.log.debug('The frontend sent /api/settings');
|
|
469
444
|
res.json(await this.getApiSettings());
|
|
470
445
|
});
|
|
471
|
-
// Endpoint to provide plugins
|
|
472
446
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
473
447
|
this.log.debug('The frontend sent /api/plugins');
|
|
474
448
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
475
449
|
});
|
|
476
|
-
// Endpoint to provide devices
|
|
477
450
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
478
451
|
this.log.debug('The frontend sent /api/devices');
|
|
479
452
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
480
453
|
});
|
|
481
|
-
// Endpoint to view the matterbridge log
|
|
482
454
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
483
455
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
484
456
|
try {
|
|
@@ -492,7 +464,6 @@ export class Frontend extends EventEmitter {
|
|
|
492
464
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
493
465
|
}
|
|
494
466
|
});
|
|
495
|
-
// Endpoint to view the matter.js log
|
|
496
467
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
497
468
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
498
469
|
try {
|
|
@@ -506,7 +477,6 @@ export class Frontend extends EventEmitter {
|
|
|
506
477
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
507
478
|
}
|
|
508
479
|
});
|
|
509
|
-
// Endpoint to view the diagnostic.log
|
|
510
480
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
511
481
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
512
482
|
await this.generateDiagnostic();
|
|
@@ -517,13 +487,10 @@ export class Frontend extends EventEmitter {
|
|
|
517
487
|
res.send(data.slice(29));
|
|
518
488
|
}
|
|
519
489
|
catch (error) {
|
|
520
|
-
// istanbul ignore next
|
|
521
490
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
522
|
-
// istanbul ignore next
|
|
523
491
|
res.status(500).send('Error reading diagnostic log file.');
|
|
524
492
|
}
|
|
525
493
|
});
|
|
526
|
-
// Endpoint to download the diagnostic.log
|
|
527
494
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
528
495
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
529
496
|
await this.generateDiagnostic();
|
|
@@ -534,19 +501,16 @@ export class Frontend extends EventEmitter {
|
|
|
534
501
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
535
502
|
}
|
|
536
503
|
catch (error) {
|
|
537
|
-
// istanbul ignore next
|
|
538
504
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
539
505
|
}
|
|
540
506
|
res.type('text/plain');
|
|
541
507
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
542
|
-
/* istanbul ignore if */
|
|
543
508
|
if (error) {
|
|
544
509
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
545
510
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
546
511
|
}
|
|
547
512
|
});
|
|
548
513
|
});
|
|
549
|
-
// Endpoint to view the history.html
|
|
550
514
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
551
515
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
552
516
|
try {
|
|
@@ -560,7 +524,6 @@ export class Frontend extends EventEmitter {
|
|
|
560
524
|
res.status(500).send('Error reading history file.');
|
|
561
525
|
}
|
|
562
526
|
});
|
|
563
|
-
// Endpoint to download the history.html
|
|
564
527
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
565
528
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
566
529
|
try {
|
|
@@ -570,7 +533,6 @@ export class Frontend extends EventEmitter {
|
|
|
570
533
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
571
534
|
res.type('text/plain');
|
|
572
535
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
573
|
-
/* istanbul ignore if */
|
|
574
536
|
if (error) {
|
|
575
537
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
576
538
|
res.status(500).send('Error downloading history file');
|
|
@@ -582,7 +544,6 @@ export class Frontend extends EventEmitter {
|
|
|
582
544
|
res.status(500).send('Error reading history file.');
|
|
583
545
|
}
|
|
584
546
|
});
|
|
585
|
-
// Endpoint to view the shelly log
|
|
586
547
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
587
548
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
588
549
|
try {
|
|
@@ -596,7 +557,6 @@ export class Frontend extends EventEmitter {
|
|
|
596
557
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
597
558
|
}
|
|
598
559
|
});
|
|
599
|
-
// Endpoint to download the matterbridge log
|
|
600
560
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
601
561
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
602
562
|
const fs = await import('node:fs');
|
|
@@ -611,14 +571,12 @@ export class Frontend extends EventEmitter {
|
|
|
611
571
|
}
|
|
612
572
|
res.type('text/plain');
|
|
613
573
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
614
|
-
/* istanbul ignore if */
|
|
615
574
|
if (error) {
|
|
616
575
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
617
576
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
618
577
|
}
|
|
619
578
|
});
|
|
620
579
|
});
|
|
621
|
-
// Endpoint to download the matter log
|
|
622
580
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
623
581
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
624
582
|
const fs = await import('node:fs');
|
|
@@ -633,14 +591,12 @@ export class Frontend extends EventEmitter {
|
|
|
633
591
|
}
|
|
634
592
|
res.type('text/plain');
|
|
635
593
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
636
|
-
/* istanbul ignore if */
|
|
637
594
|
if (error) {
|
|
638
595
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
639
596
|
res.status(500).send('Error downloading the matter log file');
|
|
640
597
|
}
|
|
641
598
|
});
|
|
642
599
|
});
|
|
643
|
-
// Endpoint to download the shelly log
|
|
644
600
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
645
601
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
646
602
|
const fs = await import('node:fs');
|
|
@@ -655,91 +611,75 @@ export class Frontend extends EventEmitter {
|
|
|
655
611
|
}
|
|
656
612
|
res.type('text/plain');
|
|
657
613
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
658
|
-
/* istanbul ignore if */
|
|
659
614
|
if (error) {
|
|
660
615
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
661
616
|
res.status(500).send('Error downloading Shelly system log file');
|
|
662
617
|
}
|
|
663
618
|
});
|
|
664
619
|
});
|
|
665
|
-
// Endpoint to download the matterbridge storage directory
|
|
666
620
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
667
621
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
668
622
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
669
623
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
670
|
-
/* istanbul ignore if */
|
|
671
624
|
if (error) {
|
|
672
625
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
673
626
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
674
627
|
}
|
|
675
628
|
});
|
|
676
629
|
});
|
|
677
|
-
// Endpoint to download the matter storage file
|
|
678
630
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
679
631
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
680
632
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
681
633
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
682
|
-
/* istanbul ignore if */
|
|
683
634
|
if (error) {
|
|
684
635
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
685
636
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
686
637
|
}
|
|
687
638
|
});
|
|
688
639
|
});
|
|
689
|
-
// Endpoint to download the matterbridge plugin directory
|
|
690
640
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
691
641
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
692
642
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
693
643
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
694
|
-
/* istanbul ignore if */
|
|
695
644
|
if (error) {
|
|
696
645
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
697
646
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
698
647
|
}
|
|
699
648
|
});
|
|
700
649
|
});
|
|
701
|
-
// Endpoint to download the matterbridge plugin config files
|
|
702
650
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
703
651
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
704
652
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
705
653
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
706
|
-
/* istanbul ignore if */
|
|
707
654
|
if (error) {
|
|
708
655
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
709
656
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
710
657
|
}
|
|
711
658
|
});
|
|
712
659
|
});
|
|
713
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
714
660
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
715
661
|
this.log.debug('The frontend sent /api/download-backup');
|
|
716
662
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
717
|
-
/* istanbul ignore if */
|
|
718
663
|
if (error) {
|
|
719
664
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
720
665
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
721
666
|
}
|
|
722
667
|
});
|
|
723
668
|
});
|
|
724
|
-
// Endpoint to upload a package
|
|
725
669
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
726
670
|
const { filename } = req.body;
|
|
727
671
|
const file = req.file;
|
|
728
|
-
/* istanbul ignore if */
|
|
729
672
|
if (!file || !filename) {
|
|
730
673
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
731
674
|
res.status(400).send('Invalid request: file and filename are required');
|
|
732
675
|
return;
|
|
733
676
|
}
|
|
734
677
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
735
|
-
// Define the path where the plugin file will be saved
|
|
736
678
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
737
679
|
try {
|
|
738
|
-
// Move the uploaded file to the specified path
|
|
739
680
|
const fs = await import('node:fs');
|
|
740
681
|
await fs.promises.rename(file.path, filePath);
|
|
741
682
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
742
|
-
// Install the plugin package
|
|
743
683
|
if (filename.endsWith('.tgz')) {
|
|
744
684
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
745
685
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
@@ -759,7 +699,6 @@ export class Frontend extends EventEmitter {
|
|
|
759
699
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
760
700
|
}
|
|
761
701
|
});
|
|
762
|
-
// Fallback for routing (must be the last route)
|
|
763
702
|
this.expressApp.use((req, res) => {
|
|
764
703
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
765
704
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -769,16 +708,13 @@ export class Frontend extends EventEmitter {
|
|
|
769
708
|
async stop() {
|
|
770
709
|
this.log.debug('Stopping the frontend...');
|
|
771
710
|
const ws = await import('ws');
|
|
772
|
-
// Remove listeners from the express app
|
|
773
711
|
if (this.expressApp) {
|
|
774
712
|
this.expressApp.removeAllListeners();
|
|
775
713
|
this.expressApp = undefined;
|
|
776
714
|
this.log.debug('Frontend app closed successfully');
|
|
777
715
|
}
|
|
778
|
-
// Close the WebSocket server
|
|
779
716
|
if (this.webSocketServer) {
|
|
780
717
|
this.log.debug('Closing WebSocket server...');
|
|
781
|
-
// Close all active connections
|
|
782
718
|
this.webSocketServer.clients.forEach((client) => {
|
|
783
719
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
784
720
|
client.close();
|
|
@@ -787,7 +723,6 @@ export class Frontend extends EventEmitter {
|
|
|
787
723
|
await withTimeout(new Promise((resolve) => {
|
|
788
724
|
this.webSocketServer?.close((error) => {
|
|
789
725
|
if (error) {
|
|
790
|
-
// istanbul ignore next
|
|
791
726
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
792
727
|
}
|
|
793
728
|
else {
|
|
@@ -800,27 +735,8 @@ export class Frontend extends EventEmitter {
|
|
|
800
735
|
this.webSocketServer.removeAllListeners();
|
|
801
736
|
this.webSocketServer = undefined;
|
|
802
737
|
}
|
|
803
|
-
// Close the http server
|
|
804
738
|
if (this.httpServer) {
|
|
805
739
|
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
|
-
*/
|
|
824
740
|
this.httpServer.close();
|
|
825
741
|
this.log.debug('Http server closed successfully');
|
|
826
742
|
this.listening = false;
|
|
@@ -829,27 +745,8 @@ export class Frontend extends EventEmitter {
|
|
|
829
745
|
this.httpServer = undefined;
|
|
830
746
|
this.log.debug('Frontend http server closed successfully');
|
|
831
747
|
}
|
|
832
|
-
// Close the https server
|
|
833
748
|
if (this.httpsServer) {
|
|
834
749
|
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
|
-
*/
|
|
853
750
|
this.httpsServer.close();
|
|
854
751
|
this.log.debug('Https server closed successfully');
|
|
855
752
|
this.listening = false;
|
|
@@ -860,13 +757,7 @@ export class Frontend extends EventEmitter {
|
|
|
860
757
|
}
|
|
861
758
|
this.log.debug('Frontend stopped successfully');
|
|
862
759
|
}
|
|
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
|
-
*/
|
|
868
760
|
async getApiSettings() {
|
|
869
|
-
// Update the variable system information properties
|
|
870
761
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
871
762
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
872
763
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -876,7 +767,6 @@ export class Frontend extends EventEmitter {
|
|
|
876
767
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
877
768
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
878
769
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
879
|
-
// Create the matterbridge information
|
|
880
770
|
const info = {
|
|
881
771
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
882
772
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -912,15 +802,9 @@ export class Frontend extends EventEmitter {
|
|
|
912
802
|
};
|
|
913
803
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
914
804
|
}
|
|
915
|
-
/**
|
|
916
|
-
* Retrieves the reachable attribute.
|
|
917
|
-
*
|
|
918
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
919
|
-
* @returns {boolean} The reachable attribute.
|
|
920
|
-
*/
|
|
921
805
|
getReachability(device) {
|
|
922
806
|
if (this.matterbridge.hasCleanupStarted)
|
|
923
|
-
return false;
|
|
807
|
+
return false;
|
|
924
808
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
925
809
|
return false;
|
|
926
810
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -931,15 +815,9 @@ export class Frontend extends EventEmitter {
|
|
|
931
815
|
return true;
|
|
932
816
|
return false;
|
|
933
817
|
}
|
|
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
|
-
*/
|
|
940
818
|
getPowerSource(endpoint) {
|
|
941
819
|
if (this.matterbridge.hasCleanupStarted)
|
|
942
|
-
return;
|
|
820
|
+
return;
|
|
943
821
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
944
822
|
return undefined;
|
|
945
823
|
const powerSource = (device) => {
|
|
@@ -954,25 +832,16 @@ export class Frontend extends EventEmitter {
|
|
|
954
832
|
}
|
|
955
833
|
return;
|
|
956
834
|
};
|
|
957
|
-
// Root endpoint
|
|
958
835
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
959
836
|
return powerSource(endpoint);
|
|
960
|
-
// Child endpoints
|
|
961
837
|
for (const child of endpoint.getChildEndpoints()) {
|
|
962
838
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
963
839
|
return powerSource(child);
|
|
964
840
|
}
|
|
965
841
|
}
|
|
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
|
-
*/
|
|
973
842
|
getClusterTextFromDevice(device) {
|
|
974
843
|
if (this.matterbridge.hasCleanupStarted)
|
|
975
|
-
return '';
|
|
844
|
+
return '';
|
|
976
845
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
977
846
|
return '';
|
|
978
847
|
const getUserLabel = (device) => {
|
|
@@ -982,7 +851,6 @@ export class Frontend extends EventEmitter {
|
|
|
982
851
|
if (composed)
|
|
983
852
|
return 'Composed: ' + composed.value;
|
|
984
853
|
}
|
|
985
|
-
// istanbul ignore next cause is not reachable
|
|
986
854
|
return '';
|
|
987
855
|
};
|
|
988
856
|
const getFixedLabel = (device) => {
|
|
@@ -992,13 +860,11 @@ export class Frontend extends EventEmitter {
|
|
|
992
860
|
if (composed)
|
|
993
861
|
return 'Composed: ' + composed.value;
|
|
994
862
|
}
|
|
995
|
-
// istanbul ignore next cause is not reacheable
|
|
996
863
|
return '';
|
|
997
864
|
};
|
|
998
865
|
let attributes = '';
|
|
999
866
|
let supportedModes = [];
|
|
1000
867
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1001
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1002
868
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1003
869
|
return;
|
|
1004
870
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1088,17 +954,11 @@ export class Frontend extends EventEmitter {
|
|
|
1088
954
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1089
955
|
attributes += `${getUserLabel(device)} `;
|
|
1090
956
|
});
|
|
1091
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1092
957
|
return attributes.trimStart().trimEnd();
|
|
1093
958
|
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1096
|
-
*
|
|
1097
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1098
|
-
*/
|
|
1099
959
|
getPlugins() {
|
|
1100
960
|
if (this.matterbridge.hasCleanupStarted)
|
|
1101
|
-
return [];
|
|
961
|
+
return [];
|
|
1102
962
|
const plugins = [];
|
|
1103
963
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1104
964
|
plugins.push({
|
|
@@ -1126,27 +986,18 @@ export class Frontend extends EventEmitter {
|
|
|
1126
986
|
schemaJson: plugin.schemaJson,
|
|
1127
987
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1128
988
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1129
|
-
// Childbridge mode specific data
|
|
1130
989
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1131
990
|
});
|
|
1132
991
|
}
|
|
1133
992
|
return plugins;
|
|
1134
993
|
}
|
|
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
|
-
*/
|
|
1141
994
|
getDevices(pluginName) {
|
|
1142
995
|
if (this.matterbridge.hasCleanupStarted)
|
|
1143
|
-
return [];
|
|
996
|
+
return [];
|
|
1144
997
|
const devices = [];
|
|
1145
998
|
for (const device of this.matterbridge.devices.array()) {
|
|
1146
|
-
// Filter by pluginName if provided
|
|
1147
999
|
if (pluginName && pluginName !== device.plugin)
|
|
1148
1000
|
continue;
|
|
1149
|
-
// Check if the device has the required properties
|
|
1150
1001
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1151
1002
|
continue;
|
|
1152
1003
|
devices.push({
|
|
@@ -1166,39 +1017,24 @@ export class Frontend extends EventEmitter {
|
|
|
1166
1017
|
}
|
|
1167
1018
|
return devices;
|
|
1168
1019
|
}
|
|
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
|
-
*/
|
|
1178
1020
|
getClusters(pluginName, endpointNumber) {
|
|
1179
1021
|
if (this.matterbridge.hasCleanupStarted)
|
|
1180
|
-
return;
|
|
1022
|
+
return;
|
|
1181
1023
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1182
1024
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1183
1025
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1184
1026
|
return;
|
|
1185
1027
|
}
|
|
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
|
|
1188
1028
|
const deviceTypes = [];
|
|
1189
1029
|
const clusters = [];
|
|
1190
1030
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1191
1031
|
deviceTypes.push(d.deviceType);
|
|
1192
1032
|
});
|
|
1193
|
-
// Get the clusters from the main endpoint
|
|
1194
1033
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1195
1034
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1196
1035
|
return;
|
|
1197
1036
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1198
1037
|
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
|
-
// );
|
|
1202
1038
|
clusters.push({
|
|
1203
1039
|
endpoint: endpoint.number.toString(),
|
|
1204
1040
|
number: endpoint.number,
|
|
@@ -1212,19 +1048,12 @@ export class Frontend extends EventEmitter {
|
|
|
1212
1048
|
attributeLocalValue: attributeValue,
|
|
1213
1049
|
});
|
|
1214
1050
|
});
|
|
1215
|
-
// Get the child endpoints
|
|
1216
1051
|
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
|
-
// }
|
|
1220
1052
|
childEndpoints.forEach((childEndpoint) => {
|
|
1221
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1222
1053
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1223
1054
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1224
1055
|
return;
|
|
1225
1056
|
}
|
|
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
|
|
1228
1057
|
const deviceTypes = [];
|
|
1229
1058
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1230
1059
|
deviceTypes.push(d.deviceType);
|
|
@@ -1234,9 +1063,6 @@ export class Frontend extends EventEmitter {
|
|
|
1234
1063
|
return;
|
|
1235
1064
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1236
1065
|
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
|
-
// );
|
|
1240
1066
|
clusters.push({
|
|
1241
1067
|
endpoint: childEndpoint.number.toString(),
|
|
1242
1068
|
number: childEndpoint.number,
|
|
@@ -1256,7 +1082,6 @@ export class Frontend extends EventEmitter {
|
|
|
1256
1082
|
async generateDiagnostic() {
|
|
1257
1083
|
this.log.debug('Generating diagnostic...');
|
|
1258
1084
|
const serverNodes = [];
|
|
1259
|
-
// istanbul ignore else
|
|
1260
1085
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1261
1086
|
if (this.matterbridge.serverNode)
|
|
1262
1087
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1267,7 +1092,6 @@ export class Frontend extends EventEmitter {
|
|
|
1267
1092
|
serverNodes.push(plugin.serverNode);
|
|
1268
1093
|
}
|
|
1269
1094
|
}
|
|
1270
|
-
// istanbul ignore next
|
|
1271
1095
|
for (const device of this.matterbridge.devices.array()) {
|
|
1272
1096
|
if (device.serverNode)
|
|
1273
1097
|
serverNodes.push(device.serverNode);
|
|
@@ -1291,15 +1115,8 @@ export class Frontend extends EventEmitter {
|
|
|
1291
1115
|
values: [...serverNodes],
|
|
1292
1116
|
})));
|
|
1293
1117
|
delete Logger.destinations.diagnostic;
|
|
1294
|
-
await wait(500);
|
|
1118
|
+
await wait(500);
|
|
1295
1119
|
}
|
|
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
|
-
*/
|
|
1303
1120
|
async wsMessageHandler(client, message) {
|
|
1304
1121
|
let data;
|
|
1305
1122
|
const sendResponse = (data) => {
|
|
@@ -1319,7 +1136,7 @@ export class Frontend extends EventEmitter {
|
|
|
1319
1136
|
};
|
|
1320
1137
|
try {
|
|
1321
1138
|
data = JSON.parse(message.toString());
|
|
1322
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1139
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1323
1140
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1324
1141
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1325
1142
|
return;
|
|
@@ -1393,7 +1210,6 @@ export class Frontend extends EventEmitter {
|
|
|
1393
1210
|
return;
|
|
1394
1211
|
})
|
|
1395
1212
|
.catch((_error) => {
|
|
1396
|
-
//
|
|
1397
1213
|
});
|
|
1398
1214
|
}
|
|
1399
1215
|
else {
|
|
@@ -1441,7 +1257,6 @@ export class Frontend extends EventEmitter {
|
|
|
1441
1257
|
return;
|
|
1442
1258
|
})
|
|
1443
1259
|
.catch((_error) => {
|
|
1444
|
-
//
|
|
1445
1260
|
});
|
|
1446
1261
|
}
|
|
1447
1262
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1467,7 +1282,6 @@ export class Frontend extends EventEmitter {
|
|
|
1467
1282
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1468
1283
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1469
1284
|
if (plugin.serverNode) {
|
|
1470
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1471
1285
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1472
1286
|
plugin.serverNode = undefined;
|
|
1473
1287
|
}
|
|
@@ -1477,20 +1291,18 @@ export class Frontend extends EventEmitter {
|
|
|
1477
1291
|
this.matterbridge.devices.remove(device);
|
|
1478
1292
|
}
|
|
1479
1293
|
}
|
|
1480
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1481
1294
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1482
1295
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1483
1296
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1484
|
-
plugin.restartRequired = false;
|
|
1297
|
+
plugin.restartRequired = false;
|
|
1485
1298
|
let needRestart = 0;
|
|
1486
1299
|
for (const plugin of this.matterbridge.plugins) {
|
|
1487
1300
|
if (plugin.restartRequired)
|
|
1488
1301
|
needRestart++;
|
|
1489
1302
|
}
|
|
1490
1303
|
if (needRestart === 0) {
|
|
1491
|
-
this.wssSendRestartNotRequired(true);
|
|
1304
|
+
this.wssSendRestartNotRequired(true);
|
|
1492
1305
|
}
|
|
1493
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1494
1306
|
if (plugin.serverNode)
|
|
1495
1307
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1496
1308
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1741,6 +1553,7 @@ export class Frontend extends EventEmitter {
|
|
|
1741
1553
|
case 'setpassword':
|
|
1742
1554
|
if (isValidString(data.params.value)) {
|
|
1743
1555
|
await this.matterbridge.nodeContext?.set('password', data.params.value);
|
|
1556
|
+
this.storedPassword = data.params.value;
|
|
1744
1557
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1745
1558
|
}
|
|
1746
1559
|
break;
|
|
@@ -1755,22 +1568,22 @@ export class Frontend extends EventEmitter {
|
|
|
1755
1568
|
if (isValidString(data.params.value, 4)) {
|
|
1756
1569
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1757
1570
|
if (data.params.value === 'Debug') {
|
|
1758
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1571
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1759
1572
|
}
|
|
1760
1573
|
else if (data.params.value === 'Info') {
|
|
1761
|
-
await this.matterbridge.setLogLevel("info"
|
|
1574
|
+
await this.matterbridge.setLogLevel("info");
|
|
1762
1575
|
}
|
|
1763
1576
|
else if (data.params.value === 'Notice') {
|
|
1764
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1577
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1765
1578
|
}
|
|
1766
1579
|
else if (data.params.value === 'Warn') {
|
|
1767
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1580
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1768
1581
|
}
|
|
1769
1582
|
else if (data.params.value === 'Error') {
|
|
1770
|
-
await this.matterbridge.setLogLevel("error"
|
|
1583
|
+
await this.matterbridge.setLogLevel("error");
|
|
1771
1584
|
}
|
|
1772
1585
|
else if (data.params.value === 'Fatal') {
|
|
1773
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1586
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1774
1587
|
}
|
|
1775
1588
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1776
1589
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1781,7 +1594,6 @@ export class Frontend extends EventEmitter {
|
|
|
1781
1594
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1782
1595
|
this.matterbridge.fileLogger = data.params.value;
|
|
1783
1596
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1784
|
-
// Create the file logger for matterbridge
|
|
1785
1597
|
if (data.params.value)
|
|
1786
1598
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1787
1599
|
else
|
|
@@ -1810,12 +1622,11 @@ export class Frontend extends EventEmitter {
|
|
|
1810
1622
|
else if (data.params.value === 'Fatal') {
|
|
1811
1623
|
Logger.level = MatterLogLevel.FATAL;
|
|
1812
1624
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1625
|
+
let callbackLogLevel = "notice";
|
|
1626
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1627
|
+
callbackLogLevel = "info";
|
|
1628
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1629
|
+
callbackLogLevel = "debug";
|
|
1819
1630
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1820
1631
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1821
1632
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1867,7 +1678,6 @@ export class Frontend extends EventEmitter {
|
|
|
1867
1678
|
}
|
|
1868
1679
|
break;
|
|
1869
1680
|
case 'setmatterport':
|
|
1870
|
-
// eslint-disable-next-line no-case-declarations
|
|
1871
1681
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1872
1682
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1873
1683
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1887,7 +1697,6 @@ export class Frontend extends EventEmitter {
|
|
|
1887
1697
|
}
|
|
1888
1698
|
break;
|
|
1889
1699
|
case 'setmatterdiscriminator':
|
|
1890
|
-
// eslint-disable-next-line no-case-declarations
|
|
1891
1700
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1892
1701
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1893
1702
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1907,7 +1716,6 @@ export class Frontend extends EventEmitter {
|
|
|
1907
1716
|
}
|
|
1908
1717
|
break;
|
|
1909
1718
|
case 'setmatterpasscode':
|
|
1910
|
-
// eslint-disable-next-line no-case-declarations
|
|
1911
1719
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1912
1720
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1913
1721
|
this.matterbridge.passcode = passcode;
|
|
@@ -1953,19 +1761,15 @@ export class Frontend extends EventEmitter {
|
|
|
1953
1761
|
return;
|
|
1954
1762
|
}
|
|
1955
1763
|
const config = plugin.configJson;
|
|
1956
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1957
1764
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1958
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1959
1765
|
if (select === 'serial')
|
|
1960
1766
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1961
1767
|
if (select === 'name')
|
|
1962
1768
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1963
1769
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1964
|
-
// Remove postfix from the serial if it exists
|
|
1965
1770
|
if (config.postfix) {
|
|
1966
1771
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1967
1772
|
}
|
|
1968
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1969
1773
|
if (isValidArray(config.whiteList, 1)) {
|
|
1970
1774
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1971
1775
|
config.whiteList.push(data.params.serial);
|
|
@@ -1974,7 +1778,6 @@ export class Frontend extends EventEmitter {
|
|
|
1974
1778
|
config.whiteList.push(data.params.name);
|
|
1975
1779
|
}
|
|
1976
1780
|
}
|
|
1977
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1978
1781
|
if (isValidArray(config.blackList, 1)) {
|
|
1979
1782
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1980
1783
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2002,9 +1805,7 @@ export class Frontend extends EventEmitter {
|
|
|
2002
1805
|
return;
|
|
2003
1806
|
}
|
|
2004
1807
|
const config = plugin.configJson;
|
|
2005
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2006
1808
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2007
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2008
1809
|
if (select === 'serial')
|
|
2009
1810
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2010
1811
|
if (select === 'name')
|
|
@@ -2013,7 +1814,6 @@ export class Frontend extends EventEmitter {
|
|
|
2013
1814
|
if (config.postfix) {
|
|
2014
1815
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2015
1816
|
}
|
|
2016
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2017
1817
|
if (isValidArray(config.whiteList, 1)) {
|
|
2018
1818
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2019
1819
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2022,7 +1822,6 @@ export class Frontend extends EventEmitter {
|
|
|
2022
1822
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2023
1823
|
}
|
|
2024
1824
|
}
|
|
2025
|
-
// Add the serial to the blackList
|
|
2026
1825
|
if (isValidArray(config.blackList)) {
|
|
2027
1826
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2028
1827
|
config.blackList.push(data.params.serial);
|
|
@@ -2045,7 +1844,6 @@ export class Frontend extends EventEmitter {
|
|
|
2045
1844
|
}
|
|
2046
1845
|
}
|
|
2047
1846
|
else {
|
|
2048
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2049
1847
|
const localData = data;
|
|
2050
1848
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2051
1849
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2055,46 +1853,23 @@ export class Frontend extends EventEmitter {
|
|
|
2055
1853
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2056
1854
|
}
|
|
2057
1855
|
}
|
|
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
|
-
*/
|
|
2071
1856
|
wssSendLogMessage(level, time, name, message) {
|
|
2072
1857
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2073
1858
|
return;
|
|
2074
1859
|
if (!level || !time || !name || !message)
|
|
2075
1860
|
return;
|
|
2076
|
-
// Remove ANSI escape codes from the message
|
|
2077
|
-
// eslint-disable-next-line no-control-regex
|
|
2078
1861
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2079
|
-
// Remove leading asterisks from the message
|
|
2080
1862
|
message = message.replace(/^\*+/, '');
|
|
2081
|
-
// Replace all occurrences of \t and \n
|
|
2082
1863
|
message = message.replace(/[\t\n]/g, '');
|
|
2083
|
-
// Remove non-printable characters
|
|
2084
|
-
// eslint-disable-next-line no-control-regex
|
|
2085
1864
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2086
|
-
// Replace all occurrences of \" with "
|
|
2087
1865
|
message = message.replace(/\\"/g, '"');
|
|
2088
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2089
1866
|
const maxContinuousLength = 100;
|
|
2090
1867
|
const keepStartLength = 20;
|
|
2091
1868
|
const keepEndLength = 20;
|
|
2092
|
-
// Split the message into words
|
|
2093
1869
|
if (level !== 'spawn') {
|
|
2094
1870
|
message = message
|
|
2095
1871
|
.split(' ')
|
|
2096
1872
|
.map((word) => {
|
|
2097
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2098
1873
|
if (word.length > maxContinuousLength) {
|
|
2099
1874
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2100
1875
|
}
|
|
@@ -2102,34 +1877,14 @@ export class Frontend extends EventEmitter {
|
|
|
2102
1877
|
})
|
|
2103
1878
|
.join(' ');
|
|
2104
1879
|
}
|
|
2105
|
-
// Send the message to all connected clients
|
|
2106
1880
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2107
1881
|
}
|
|
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
|
-
*/
|
|
2120
1882
|
wssSendRefreshRequired(changed, params) {
|
|
2121
1883
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2122
1884
|
return;
|
|
2123
1885
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2124
|
-
// Send the message to all connected clients
|
|
2125
1886
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2126
1887
|
}
|
|
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
|
-
*/
|
|
2133
1888
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2134
1889
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2135
1890
|
return;
|
|
@@ -2138,14 +1893,8 @@ export class Frontend extends EventEmitter {
|
|
|
2138
1893
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2139
1894
|
if (snackbar === true)
|
|
2140
1895
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2141
|
-
// Send the message to all connected clients
|
|
2142
1896
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2143
1897
|
}
|
|
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
|
-
*/
|
|
2149
1898
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2150
1899
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2151
1900
|
return;
|
|
@@ -2153,133 +1902,57 @@ export class Frontend extends EventEmitter {
|
|
|
2153
1902
|
this.matterbridge.restartRequired = false;
|
|
2154
1903
|
if (snackbar === true)
|
|
2155
1904
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2156
|
-
// Send the message to all connected clients
|
|
2157
1905
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2158
1906
|
}
|
|
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
|
-
*/
|
|
2164
1907
|
wssSendUpdateRequired(devVersion = false) {
|
|
2165
1908
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2166
1909
|
return;
|
|
2167
1910
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2168
1911
|
this.matterbridge.updateRequired = true;
|
|
2169
|
-
// Send the message to all connected clients
|
|
2170
1912
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2171
1913
|
}
|
|
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
|
-
*/
|
|
2178
1914
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2179
1915
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2180
1916
|
return;
|
|
2181
1917
|
if (hasParameter('debug'))
|
|
2182
1918
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2183
|
-
// Send the message to all connected clients
|
|
2184
1919
|
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 } });
|
|
2185
1920
|
}
|
|
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
|
-
*/
|
|
2197
1921
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2198
1922
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2199
1923
|
return;
|
|
2200
1924
|
if (hasParameter('debug'))
|
|
2201
1925
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2202
|
-
// Send the message to all connected clients
|
|
2203
1926
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2204
1927
|
}
|
|
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
|
-
*/
|
|
2211
1928
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2212
1929
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2213
1930
|
return;
|
|
2214
1931
|
if (hasParameter('debug'))
|
|
2215
1932
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2216
|
-
// Send the message to all connected clients
|
|
2217
1933
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2218
1934
|
}
|
|
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
|
-
*/
|
|
2230
1935
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2231
1936
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2232
1937
|
return;
|
|
2233
1938
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2234
|
-
// Send the message to all connected clients
|
|
2235
1939
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2236
1940
|
}
|
|
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
|
-
*/
|
|
2243
1941
|
wssSendCloseSnackbarMessage(message) {
|
|
2244
1942
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2245
1943
|
return;
|
|
2246
1944
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2247
|
-
// Send the message to all connected clients
|
|
2248
1945
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2249
1946
|
}
|
|
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
|
-
*/
|
|
2266
1947
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2267
1948
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2268
1949
|
return;
|
|
2269
1950
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2270
|
-
// Send the message to all connected clients
|
|
2271
1951
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2272
1952
|
}
|
|
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
|
-
*/
|
|
2279
1953
|
wssBroadcastMessage(msg) {
|
|
2280
1954
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2281
1955
|
return;
|
|
2282
|
-
// Send the message to all connected clients
|
|
2283
1956
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2284
1957
|
if (msg.method !== 'log')
|
|
2285
1958
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -2290,4 +1963,3 @@ export class Frontend extends EventEmitter {
|
|
|
2290
1963
|
});
|
|
2291
1964
|
}
|
|
2292
1965
|
}
|
|
2293
|
-
//# sourceMappingURL=frontend.js.map
|