matterbridge 3.5.0 → 3.5.1-dev-20260122-6461be3
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 +18 -0
- package/README-DOCKER.md +4 -2
- package/README.md +4 -1
- package/bin/mb_coap.js +1 -1
- package/bin/mb_mdns.js +1 -1
- package/dist/broadcastServer.d.ts +0 -115
- package/dist/broadcastServer.js +1 -119
- package/dist/broadcastServerTypes.d.ts +0 -43
- package/dist/broadcastServerTypes.js +0 -24
- package/dist/cli.d.ts +1 -26
- package/dist/cli.js +2 -102
- package/dist/cliEmitter.d.ts +0 -36
- package/dist/cliEmitter.js +0 -37
- package/dist/cliHistory.d.ts +0 -42
- package/dist/cliHistory.js +1 -39
- package/dist/clusters/export.d.ts +0 -1
- package/dist/clusters/export.js +0 -2
- package/dist/deviceManager.d.ts +0 -108
- package/dist/deviceManager.js +2 -114
- package/dist/devices/airConditioner.d.ts +0 -75
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/batteryStorage.d.ts +0 -43
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.d.ts +0 -55
- package/dist/devices/cooktop.js +0 -56
- package/dist/devices/dishwasher.d.ts +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.d.ts +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.d.ts +0 -1
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.d.ts +0 -41
- package/dist/devices/extractorHood.js +0 -43
- package/dist/devices/heatPump.d.ts +0 -43
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.d.ts +0 -58
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.d.ts +0 -64
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.d.ts +1 -77
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.d.ts +0 -82
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.d.ts +0 -100
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.d.ts +0 -36
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.d.ts +0 -79
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.d.ts +0 -21
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.d.ts +0 -74
- package/dist/devices/waterHeater.js +2 -82
- package/dist/frontend.d.ts +4 -187
- package/dist/frontend.js +89 -505
- package/dist/frontendTypes.d.ts +0 -57
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.d.ts +0 -43
- package/dist/helpers.js +1 -54
- package/dist/index.d.ts +0 -23
- package/dist/index.js +0 -25
- package/dist/jestutils/export.d.ts +0 -1
- package/dist/jestutils/export.js +0 -1
- package/dist/jestutils/jestHelpers.d.ts +0 -255
- package/dist/jestutils/jestHelpers.js +16 -379
- package/dist/logger/export.d.ts +0 -1
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.d.ts +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.d.ts +0 -1
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.d.ts +0 -1
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.d.ts +0 -1
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.d.ts +0 -1
- package/dist/matter/export.js +0 -2
- package/dist/matter/types.d.ts +0 -1
- package/dist/matter/types.js +0 -2
- package/dist/matterNode.d.ts +0 -258
- package/dist/matterNode.js +9 -364
- package/dist/matterbridge.d.ts +0 -362
- package/dist/matterbridge.js +75 -864
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -36
- package/dist/matterbridgeAccessoryPlatform.js +0 -38
- package/dist/matterbridgeBehaviors.d.ts +0 -24
- package/dist/matterbridgeBehaviors.js +5 -68
- package/dist/matterbridgeDeviceTypes.d.ts +0 -649
- package/dist/matterbridgeDeviceTypes.js +6 -673
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -36
- package/dist/matterbridgeDynamicPlatform.js +0 -38
- package/dist/matterbridgeEndpoint.d.ts +2 -1332
- package/dist/matterbridgeEndpoint.js +94 -1459
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
- package/dist/matterbridgeEndpointHelpers.js +22 -487
- package/dist/matterbridgeEndpointTypes.d.ts +0 -70
- package/dist/matterbridgeEndpointTypes.js +0 -25
- package/dist/matterbridgePlatform.d.ts +0 -425
- package/dist/matterbridgePlatform.js +2 -453
- package/dist/matterbridgeTypes.d.ts +0 -46
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/mb_coap.d.ts +1 -0
- package/dist/{dgram/mb_coap.js → mb_coap.js} +3 -41
- package/dist/mb_mdns.d.ts +1 -0
- package/dist/{dgram/mb_mdns.js → mb_mdns.js} +47 -81
- package/dist/pluginManager.d.ts +0 -305
- package/dist/pluginManager.js +8 -345
- package/dist/shelly.d.ts +0 -157
- package/dist/shelly.js +7 -178
- package/dist/spawn.d.ts +1 -0
- package/dist/{utils/spawn.js → spawn.js} +3 -73
- package/dist/storage/export.d.ts +0 -1
- package/dist/storage/export.js +0 -1
- package/dist/update.d.ts +0 -75
- package/dist/update.js +7 -100
- package/dist/utils/export.d.ts +1 -13
- package/dist/utils/export.js +1 -13
- package/dist/workerGlobalPrefix.d.ts +0 -24
- package/dist/workerGlobalPrefix.js +6 -40
- package/dist/workerTypes.d.ts +0 -25
- package/dist/workerTypes.js +0 -24
- package/dist/workers.d.ts +0 -61
- package/dist/workers.js +4 -68
- package/npm-shrinkwrap.json +80 -50
- package/package.json +8 -8
- package/dist/broadcastServer.d.ts.map +0 -1
- package/dist/broadcastServer.js.map +0 -1
- package/dist/broadcastServerTypes.d.ts.map +0 -1
- package/dist/broadcastServerTypes.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/cliHistory.d.ts.map +0 -1
- package/dist/cliHistory.js.map +0 -1
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- 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 +0 -365
- package/dist/dgram/coap.js.map +0 -1
- package/dist/dgram/dgram.d.ts +0 -144
- package/dist/dgram/dgram.d.ts.map +0 -1
- package/dist/dgram/dgram.js +0 -363
- 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 -371
- package/dist/dgram/mdns.d.ts.map +0 -1
- package/dist/dgram/mdns.js +0 -934
- 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 +0 -179
- package/dist/dgram/multicast.js.map +0 -1
- package/dist/dgram/unicast.d.ts +0 -64
- package/dist/dgram/unicast.d.ts.map +0 -1
- package/dist/dgram/unicast.js +0 -100
- package/dist/dgram/unicast.js.map +0 -1
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/jestutils/export.d.ts.map +0 -1
- package/dist/jestutils/export.js.map +0 -1
- package/dist/jestutils/jestHelpers.d.ts.map +0 -1
- package/dist/jestutils/jestHelpers.js.map +0 -1
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterNode.d.ts.map +0 -1
- package/dist/matterNode.js.map +0 -1
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
- package/dist/matterbridgeEndpointTypes.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -101
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js +0 -282
- 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 +0 -123
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -35
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js +0 -76
- 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 +0 -54
- 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 +0 -114
- 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 +0 -79
- 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 +0 -129
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/error.d.ts +0 -45
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/error.js +0 -54
- package/dist/utils/error.js.map +0 -1
- 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 +0 -78
- 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 +0 -242
- 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 +0 -268
- 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 +0 -162
- package/dist/utils/isValid.js.map +0 -1
- package/dist/utils/network.d.ts +0 -141
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js +0 -314
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -33
- 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 +0 -264
- 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 +0 -125
- package/dist/utils/wait.js.map +0 -1
- package/dist/workerGlobalPrefix.d.ts.map +0 -1
- package/dist/workerGlobalPrefix.js.map +0 -1
- package/dist/workerTypes.d.ts.map +0 -1
- package/dist/workerTypes.js.map +0 -1
- package/dist/workers.d.ts.map +0 -1
- package/dist/workers.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.3
|
|
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 */ /* istanbul ignore next */
|
|
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';
|
|
@@ -36,13 +10,8 @@ import { FabricIndex } from '@matter/types/datatype';
|
|
|
36
10
|
import { CommissioningOptions } from '@matter/types/commissioning';
|
|
37
11
|
import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
|
|
38
12
|
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
13
|
+
import { createZip, formatBytes, formatPercent, formatUptime, getParameter, hasParameter, inspectError, isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString, wait, withTimeout } from '@matterbridge/utils';
|
|
39
14
|
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
|
|
40
|
-
import { isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/isValid.js';
|
|
41
|
-
import { createZip } from './utils/createZip.js';
|
|
42
|
-
import { hasParameter, getParameter } from './utils/commandLine.js';
|
|
43
|
-
import { withTimeout, wait } from './utils/wait.js';
|
|
44
|
-
import { inspectError } from './utils/error.js';
|
|
45
|
-
import { formatBytes, formatUptime, formatPercent } from './utils/format.js';
|
|
46
15
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
47
16
|
import { cliEmitter, lastOsCpuUsage, lastProcessCpuUsage } from './cliEmitter.js';
|
|
48
17
|
import { generateHistoryPage } from './cliHistory.js';
|
|
@@ -53,6 +22,7 @@ export class Frontend extends EventEmitter {
|
|
|
53
22
|
port = 8283;
|
|
54
23
|
listening = false;
|
|
55
24
|
storedPassword = undefined;
|
|
25
|
+
authClients = [];
|
|
56
26
|
expressApp;
|
|
57
27
|
httpServer;
|
|
58
28
|
httpsServer;
|
|
@@ -63,7 +33,7 @@ export class Frontend extends EventEmitter {
|
|
|
63
33
|
constructor(matterbridge) {
|
|
64
34
|
super();
|
|
65
35
|
this.matterbridge = matterbridge;
|
|
66
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
36
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
67
37
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
68
38
|
this.server = new BroadcastServer('frontend', this.log);
|
|
69
39
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -74,7 +44,6 @@ export class Frontend extends EventEmitter {
|
|
|
74
44
|
}
|
|
75
45
|
async msgHandler(msg) {
|
|
76
46
|
if (this.server.isWorkerRequest(msg)) {
|
|
77
|
-
// istanbul ignore else
|
|
78
47
|
if (this.verbose)
|
|
79
48
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
80
49
|
switch (msg.type) {
|
|
@@ -126,13 +95,11 @@ export class Frontend extends EventEmitter {
|
|
|
126
95
|
this.server.respond({ ...msg, result: { success: true } });
|
|
127
96
|
break;
|
|
128
97
|
default:
|
|
129
|
-
// istanbul ignore next
|
|
130
98
|
if (this.verbose)
|
|
131
99
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
132
100
|
}
|
|
133
101
|
}
|
|
134
102
|
if (this.server.isWorkerResponse(msg) && msg.result) {
|
|
135
|
-
// istanbul ignore next
|
|
136
103
|
if (this.verbose)
|
|
137
104
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
138
105
|
switch (msg.type) {
|
|
@@ -164,59 +131,35 @@ export class Frontend extends EventEmitter {
|
|
|
164
131
|
set logLevel(logLevel) {
|
|
165
132
|
this.log.logLevel = logLevel;
|
|
166
133
|
}
|
|
134
|
+
validateReq(req, res) {
|
|
135
|
+
if (req.ip && !this.authClients.includes(req.ip)) {
|
|
136
|
+
this.log.warn(`Warning blocked unauthorized access request ${req.originalUrl ?? req.url} from ${req.ip}`);
|
|
137
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
167
142
|
async start(port = 8283) {
|
|
168
143
|
this.port = port;
|
|
169
144
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
170
145
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
171
|
-
// Initialize multer with the upload directory
|
|
172
146
|
const multer = await import('multer');
|
|
173
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
147
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
174
148
|
const upload = multer.default({ dest: uploadDir });
|
|
175
|
-
// Create the express app that serves the frontend
|
|
176
149
|
const express = await import('express');
|
|
177
150
|
this.expressApp = express.default();
|
|
178
|
-
// Inject logging/debug wrapper for route/middleware registration
|
|
179
|
-
/*
|
|
180
|
-
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
181
|
-
for (const method of methods) {
|
|
182
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
|
-
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
184
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
-
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
186
|
-
try {
|
|
187
|
-
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
188
|
-
return original(path, ...rest);
|
|
189
|
-
} catch (err) {
|
|
190
|
-
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
191
|
-
throw err;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
*/
|
|
196
|
-
// Log all requests to the server for debugging
|
|
197
|
-
/*
|
|
198
|
-
this.expressApp.use((req, res, next) => {
|
|
199
|
-
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
200
|
-
next();
|
|
201
|
-
});
|
|
202
|
-
*/
|
|
203
|
-
// Serve static files from 'frontend/build' directory
|
|
204
151
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
|
|
205
|
-
// Create a WebSocket server and attach it to the http or https server
|
|
206
152
|
this.log.debug(`Creating WebSocketServer...`);
|
|
207
153
|
const ws = await import('ws');
|
|
208
154
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
209
155
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
210
156
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
211
157
|
const clientIp = request.socket.remoteAddress;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (this.matterbridge.getLogLevel() === "
|
|
216
|
-
callbackLogLevel = "
|
|
217
|
-
// istanbul ignore else
|
|
218
|
-
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
219
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
158
|
+
let callbackLogLevel = "notice";
|
|
159
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
160
|
+
callbackLogLevel = "info";
|
|
161
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
162
|
+
callbackLogLevel = "debug";
|
|
220
163
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
221
164
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
222
165
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -232,33 +175,23 @@ export class Frontend extends EventEmitter {
|
|
|
232
175
|
});
|
|
233
176
|
ws.on('close', () => {
|
|
234
177
|
this.log.info('WebSocket client disconnected');
|
|
235
|
-
// istanbul ignore else
|
|
236
178
|
if (this.webSocketServer?.clients.size === 0) {
|
|
237
179
|
AnsiLogger.setGlobalCallback(undefined);
|
|
238
180
|
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
181
|
+
this.authClients = [];
|
|
239
182
|
}
|
|
240
183
|
});
|
|
241
|
-
// istanbul ignore next
|
|
242
184
|
ws.on('error', (error) => {
|
|
243
|
-
// istanbul ignore next
|
|
244
185
|
this.log.error(`WebSocket client error: ${error}`);
|
|
245
186
|
});
|
|
246
187
|
});
|
|
247
188
|
this.webSocketServer.on('close', () => {
|
|
248
189
|
this.log.debug(`WebSocketServer closed`);
|
|
249
190
|
});
|
|
250
|
-
/* With { noServer: true } it never fires
|
|
251
|
-
this.webSocketServer.on('listening', () => {
|
|
252
|
-
this.log.info(`The WebSocketServer is listening`);
|
|
253
|
-
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
254
|
-
});
|
|
255
|
-
*/
|
|
256
|
-
// istanbul ignore next
|
|
257
191
|
this.webSocketServer.on('error', (ws, error) => {
|
|
258
192
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
259
193
|
});
|
|
260
194
|
if (!hasParameter('ssl')) {
|
|
261
|
-
// Create an HTTP server and attach the express app
|
|
262
195
|
const http = await import('node:http');
|
|
263
196
|
try {
|
|
264
197
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -269,9 +202,7 @@ export class Frontend extends EventEmitter {
|
|
|
269
202
|
this.emit('server_error', error);
|
|
270
203
|
return;
|
|
271
204
|
}
|
|
272
|
-
// Listen on the specified port
|
|
273
205
|
if (hasParameter('ingress')) {
|
|
274
|
-
// We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
|
|
275
206
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
276
207
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
277
208
|
this.listening = true;
|
|
@@ -279,17 +210,13 @@ export class Frontend extends EventEmitter {
|
|
|
279
210
|
});
|
|
280
211
|
}
|
|
281
212
|
else {
|
|
282
|
-
// We listen to all available addresses
|
|
283
213
|
this.httpServer.listen(this.port, getParameter('bind'), () => {
|
|
284
214
|
const addr = this.httpServer?.address();
|
|
285
|
-
// istanbul ignore else
|
|
286
215
|
if (addr && typeof addr !== 'string') {
|
|
287
216
|
this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
288
217
|
}
|
|
289
|
-
// istanbul ignore else
|
|
290
218
|
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
291
219
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
292
|
-
// istanbul ignore else
|
|
293
220
|
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
294
221
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
295
222
|
this.listening = true;
|
|
@@ -298,30 +225,24 @@ export class Frontend extends EventEmitter {
|
|
|
298
225
|
}
|
|
299
226
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
300
227
|
try {
|
|
301
|
-
// Only proceed for real WebSocket upgrades
|
|
302
|
-
// istanbul ignore next cause is only a safety check
|
|
303
228
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
304
229
|
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
305
230
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
306
231
|
return socket.destroy();
|
|
307
232
|
}
|
|
308
|
-
// Build a URL so we can read ?password=...
|
|
309
233
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
310
|
-
// Validate WebSocket password
|
|
311
234
|
const password = url.searchParams.get('password') ?? '';
|
|
312
235
|
if (password !== this.storedPassword) {
|
|
313
236
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
314
237
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
315
238
|
return socket.destroy();
|
|
316
239
|
}
|
|
317
|
-
// Complete the WebSocket handshake
|
|
318
240
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
319
241
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
320
242
|
this.webSocketServer?.emit('connection', ws, req);
|
|
321
243
|
});
|
|
322
244
|
}
|
|
323
245
|
catch (err) {
|
|
324
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
325
246
|
{
|
|
326
247
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
327
248
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -344,7 +265,6 @@ export class Frontend extends EventEmitter {
|
|
|
344
265
|
});
|
|
345
266
|
}
|
|
346
267
|
else {
|
|
347
|
-
// SSL is enabled, load the certificate and the private key
|
|
348
268
|
let cert;
|
|
349
269
|
let key;
|
|
350
270
|
let ca;
|
|
@@ -354,7 +274,6 @@ export class Frontend extends EventEmitter {
|
|
|
354
274
|
let httpsServerOptions = {};
|
|
355
275
|
const fs = await import('node:fs');
|
|
356
276
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
357
|
-
// Load the p12 certificate and the passphrase
|
|
358
277
|
try {
|
|
359
278
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
360
279
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -366,7 +285,7 @@ export class Frontend extends EventEmitter {
|
|
|
366
285
|
}
|
|
367
286
|
try {
|
|
368
287
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
369
|
-
passphrase = passphrase.trim();
|
|
288
|
+
passphrase = passphrase.trim();
|
|
370
289
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
371
290
|
}
|
|
372
291
|
catch (error) {
|
|
@@ -377,7 +296,6 @@ export class Frontend extends EventEmitter {
|
|
|
377
296
|
httpsServerOptions = { pfx, passphrase };
|
|
378
297
|
}
|
|
379
298
|
else {
|
|
380
|
-
// 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.
|
|
381
299
|
try {
|
|
382
300
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
383
301
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -407,10 +325,9 @@ export class Frontend extends EventEmitter {
|
|
|
407
325
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
408
326
|
}
|
|
409
327
|
if (hasParameter('mtls')) {
|
|
410
|
-
httpsServerOptions.requestCert = true;
|
|
411
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
328
|
+
httpsServerOptions.requestCert = true;
|
|
329
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
412
330
|
}
|
|
413
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
414
331
|
const https = await import('node:https');
|
|
415
332
|
try {
|
|
416
333
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -421,9 +338,7 @@ export class Frontend extends EventEmitter {
|
|
|
421
338
|
this.emit('server_error', error);
|
|
422
339
|
return;
|
|
423
340
|
}
|
|
424
|
-
// Listen on the specified port
|
|
425
341
|
if (hasParameter('ingress')) {
|
|
426
|
-
// We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
|
|
427
342
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
428
343
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
429
344
|
this.listening = true;
|
|
@@ -431,17 +346,13 @@ export class Frontend extends EventEmitter {
|
|
|
431
346
|
});
|
|
432
347
|
}
|
|
433
348
|
else {
|
|
434
|
-
// We listen to all available addresses
|
|
435
349
|
this.httpsServer.listen(this.port, getParameter('bind'), () => {
|
|
436
350
|
const addr = this.httpsServer?.address();
|
|
437
|
-
// istanbul ignore else
|
|
438
351
|
if (addr && typeof addr !== 'string') {
|
|
439
352
|
this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
440
353
|
}
|
|
441
|
-
// istanbul ignore else
|
|
442
354
|
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
443
355
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
444
|
-
// istanbul ignore else
|
|
445
356
|
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
446
357
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
447
358
|
this.listening = true;
|
|
@@ -450,29 +361,23 @@ export class Frontend extends EventEmitter {
|
|
|
450
361
|
}
|
|
451
362
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
452
363
|
try {
|
|
453
|
-
// Only proceed for real WebSocket upgrades
|
|
454
|
-
// istanbul ignore next cause is only a safety check
|
|
455
364
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
456
365
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
457
366
|
return socket.destroy();
|
|
458
367
|
}
|
|
459
|
-
// Build a URL so we can read ?password=...
|
|
460
368
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
461
|
-
// Validate WebSocket password
|
|
462
369
|
const password = url.searchParams.get('password') ?? '';
|
|
463
370
|
if (password !== this.storedPassword) {
|
|
464
371
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
465
372
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
466
373
|
return socket.destroy();
|
|
467
374
|
}
|
|
468
|
-
// Complete the WebSocket handshake
|
|
469
375
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
470
376
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
471
377
|
this.webSocketServer?.emit('connection', ws, req);
|
|
472
378
|
});
|
|
473
379
|
}
|
|
474
380
|
catch (err) {
|
|
475
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
476
381
|
{
|
|
477
382
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
478
383
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -494,7 +399,6 @@ export class Frontend extends EventEmitter {
|
|
|
494
399
|
return;
|
|
495
400
|
});
|
|
496
401
|
}
|
|
497
|
-
// Subscribe to cli events
|
|
498
402
|
cliEmitter.removeAllListeners();
|
|
499
403
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
500
404
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -505,34 +409,31 @@ export class Frontend extends EventEmitter {
|
|
|
505
409
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
506
410
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
507
411
|
});
|
|
508
|
-
// Endpoint to validate login code
|
|
509
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
510
412
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
511
413
|
const { password } = req.body;
|
|
512
414
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
513
415
|
if (this.storedPassword === '' || password === this.storedPassword) {
|
|
514
416
|
this.log.debug('/api/login password valid');
|
|
515
417
|
res.json({ valid: true });
|
|
418
|
+
if (req.ip)
|
|
419
|
+
this.authClients.push(req.ip);
|
|
516
420
|
}
|
|
517
421
|
else {
|
|
518
422
|
this.log.warn('/api/login error wrong password');
|
|
519
423
|
res.json({ valid: false });
|
|
520
424
|
}
|
|
521
425
|
});
|
|
522
|
-
// Endpoint to provide health check for docker
|
|
523
426
|
this.expressApp.get('/health', (req, res) => {
|
|
524
427
|
this.log.debug('Express received /health');
|
|
525
428
|
const healthStatus = {
|
|
526
|
-
status: 'ok',
|
|
527
|
-
uptime: process.uptime(),
|
|
528
|
-
timestamp: new Date().toISOString(),
|
|
429
|
+
status: 'ok',
|
|
430
|
+
uptime: process.uptime(),
|
|
431
|
+
timestamp: new Date().toISOString(),
|
|
529
432
|
};
|
|
530
433
|
res.status(200).json(healthStatus);
|
|
531
434
|
});
|
|
532
|
-
// Endpoint to provide memory usage details
|
|
533
435
|
this.expressApp.get('/memory', async (req, res) => {
|
|
534
436
|
this.log.debug('Express received /memory');
|
|
535
|
-
// Memory usage from process
|
|
536
437
|
const memoryUsageRaw = process.memoryUsage();
|
|
537
438
|
const memoryUsage = {
|
|
538
439
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -541,13 +442,10 @@ export class Frontend extends EventEmitter {
|
|
|
541
442
|
external: formatBytes(memoryUsageRaw.external),
|
|
542
443
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
543
444
|
};
|
|
544
|
-
// V8 heap statistics
|
|
545
445
|
const { default: v8 } = await import('node:v8');
|
|
546
446
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
547
447
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
548
|
-
// Format heapStats
|
|
549
448
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
550
|
-
// Format heapSpaces
|
|
551
449
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
552
450
|
...space,
|
|
553
451
|
space_size: formatBytes(space.space_size),
|
|
@@ -566,24 +464,28 @@ export class Frontend extends EventEmitter {
|
|
|
566
464
|
};
|
|
567
465
|
res.status(200).json(memoryReport);
|
|
568
466
|
});
|
|
569
|
-
// Endpoint to provide settings
|
|
570
467
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
571
468
|
this.log.debug('The frontend sent /api/settings');
|
|
469
|
+
if (!this.validateReq(req, res))
|
|
470
|
+
return;
|
|
572
471
|
res.json(await this.getApiSettings());
|
|
573
472
|
});
|
|
574
|
-
// Endpoint to provide plugins
|
|
575
473
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
576
474
|
this.log.debug('The frontend sent /api/plugins');
|
|
475
|
+
if (!this.validateReq(req, res))
|
|
476
|
+
return;
|
|
577
477
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
578
478
|
});
|
|
579
|
-
// Endpoint to provide devices
|
|
580
479
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
581
480
|
this.log.debug('The frontend sent /api/devices');
|
|
481
|
+
if (!this.validateReq(req, res))
|
|
482
|
+
return;
|
|
582
483
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
583
484
|
});
|
|
584
|
-
// Endpoint to view the matterbridge log
|
|
585
485
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
586
486
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
487
|
+
if (!this.validateReq(req, res))
|
|
488
|
+
return;
|
|
587
489
|
try {
|
|
588
490
|
const fs = await import('node:fs');
|
|
589
491
|
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
|
|
@@ -595,9 +497,10 @@ export class Frontend extends EventEmitter {
|
|
|
595
497
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
596
498
|
}
|
|
597
499
|
});
|
|
598
|
-
// Endpoint to view the matter.js log
|
|
599
500
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
600
501
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
502
|
+
if (!this.validateReq(req, res))
|
|
503
|
+
return;
|
|
601
504
|
try {
|
|
602
505
|
const fs = await import('node:fs');
|
|
603
506
|
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
|
|
@@ -609,9 +512,10 @@ export class Frontend extends EventEmitter {
|
|
|
609
512
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
610
513
|
}
|
|
611
514
|
});
|
|
612
|
-
// Endpoint to view the diagnostic.log
|
|
613
515
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
614
516
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
517
|
+
if (!this.validateReq(req, res))
|
|
518
|
+
return;
|
|
615
519
|
await this.generateDiagnostic();
|
|
616
520
|
try {
|
|
617
521
|
const fs = await import('node:fs');
|
|
@@ -620,15 +524,14 @@ export class Frontend extends EventEmitter {
|
|
|
620
524
|
res.send(data.slice(29));
|
|
621
525
|
}
|
|
622
526
|
catch (error) {
|
|
623
|
-
// istanbul ignore next
|
|
624
527
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
625
|
-
// istanbul ignore next
|
|
626
528
|
res.status(500).send('Error reading diagnostic log file.');
|
|
627
529
|
}
|
|
628
530
|
});
|
|
629
|
-
// Endpoint to download the diagnostic.log
|
|
630
531
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
631
532
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
533
|
+
if (!this.validateReq(req, res))
|
|
534
|
+
return;
|
|
632
535
|
await this.generateDiagnostic();
|
|
633
536
|
try {
|
|
634
537
|
const fs = await import('node:fs');
|
|
@@ -637,21 +540,20 @@ export class Frontend extends EventEmitter {
|
|
|
637
540
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
638
541
|
}
|
|
639
542
|
catch (error) {
|
|
640
|
-
// istanbul ignore next
|
|
641
543
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
642
544
|
}
|
|
643
545
|
res.type('text/plain');
|
|
644
546
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
645
|
-
/* istanbul ignore if */
|
|
646
547
|
if (error) {
|
|
647
548
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
648
549
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
649
550
|
}
|
|
650
551
|
});
|
|
651
552
|
});
|
|
652
|
-
// Endpoint to view the history.html
|
|
653
553
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
654
554
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
555
|
+
if (!this.validateReq(req, res))
|
|
556
|
+
return;
|
|
655
557
|
try {
|
|
656
558
|
const fs = await import('node:fs');
|
|
657
559
|
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), 'utf8');
|
|
@@ -663,9 +565,10 @@ export class Frontend extends EventEmitter {
|
|
|
663
565
|
res.status(500).send('Error reading history file.');
|
|
664
566
|
}
|
|
665
567
|
});
|
|
666
|
-
// Endpoint to download the history.html
|
|
667
568
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
668
569
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
570
|
+
if (!this.validateReq(req, res))
|
|
571
|
+
return;
|
|
669
572
|
try {
|
|
670
573
|
const fs = await import('node:fs');
|
|
671
574
|
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), fs.constants.F_OK);
|
|
@@ -673,7 +576,6 @@ export class Frontend extends EventEmitter {
|
|
|
673
576
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
674
577
|
res.type('text/plain');
|
|
675
578
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
676
|
-
/* istanbul ignore if */
|
|
677
579
|
if (error) {
|
|
678
580
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
679
581
|
res.status(500).send('Error downloading history file');
|
|
@@ -685,9 +587,10 @@ export class Frontend extends EventEmitter {
|
|
|
685
587
|
res.status(500).send('Error reading history file.');
|
|
686
588
|
}
|
|
687
589
|
});
|
|
688
|
-
// Endpoint to view the shelly log
|
|
689
590
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
690
591
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
592
|
+
if (!this.validateReq(req, res))
|
|
593
|
+
return;
|
|
691
594
|
try {
|
|
692
595
|
const fs = await import('node:fs');
|
|
693
596
|
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
@@ -699,9 +602,10 @@ export class Frontend extends EventEmitter {
|
|
|
699
602
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
700
603
|
}
|
|
701
604
|
});
|
|
702
|
-
// Endpoint to download the matterbridge log
|
|
703
605
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
704
606
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
607
|
+
if (!this.validateReq(req, res))
|
|
608
|
+
return;
|
|
705
609
|
const fs = await import('node:fs');
|
|
706
610
|
try {
|
|
707
611
|
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
|
|
@@ -714,16 +618,16 @@ export class Frontend extends EventEmitter {
|
|
|
714
618
|
}
|
|
715
619
|
res.type('text/plain');
|
|
716
620
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
717
|
-
/* istanbul ignore if */
|
|
718
621
|
if (error) {
|
|
719
622
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
720
623
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
721
624
|
}
|
|
722
625
|
});
|
|
723
626
|
});
|
|
724
|
-
// Endpoint to download the matter log
|
|
725
627
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
726
628
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
629
|
+
if (!this.validateReq(req, res))
|
|
630
|
+
return;
|
|
727
631
|
const fs = await import('node:fs');
|
|
728
632
|
try {
|
|
729
633
|
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
|
|
@@ -736,16 +640,16 @@ export class Frontend extends EventEmitter {
|
|
|
736
640
|
}
|
|
737
641
|
res.type('text/plain');
|
|
738
642
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
739
|
-
/* istanbul ignore if */
|
|
740
643
|
if (error) {
|
|
741
644
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
742
645
|
res.status(500).send('Error downloading the matter log file');
|
|
743
646
|
}
|
|
744
647
|
});
|
|
745
648
|
});
|
|
746
|
-
// Endpoint to download the shelly log
|
|
747
649
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
748
650
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
651
|
+
if (!this.validateReq(req, res))
|
|
652
|
+
return;
|
|
749
653
|
const fs = await import('node:fs');
|
|
750
654
|
try {
|
|
751
655
|
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
@@ -758,93 +662,89 @@ export class Frontend extends EventEmitter {
|
|
|
758
662
|
}
|
|
759
663
|
res.type('text/plain');
|
|
760
664
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
761
|
-
/* istanbul ignore if */
|
|
762
665
|
if (error) {
|
|
763
666
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
764
667
|
res.status(500).send('Error downloading Shelly system log file');
|
|
765
668
|
}
|
|
766
669
|
});
|
|
767
670
|
});
|
|
768
|
-
// Endpoint to download the matterbridge storage directory
|
|
769
671
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
770
672
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
673
|
+
if (!this.validateReq(req, res))
|
|
674
|
+
return;
|
|
771
675
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
772
676
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
773
|
-
/* istanbul ignore if */
|
|
774
677
|
if (error) {
|
|
775
678
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
776
679
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
777
680
|
}
|
|
778
681
|
});
|
|
779
682
|
});
|
|
780
|
-
// Endpoint to download the matter storage file
|
|
781
683
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
782
684
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
685
|
+
if (!this.validateReq(req, res))
|
|
686
|
+
return;
|
|
783
687
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
784
688
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
785
|
-
/* istanbul ignore if */
|
|
786
689
|
if (error) {
|
|
787
690
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
788
691
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
789
692
|
}
|
|
790
693
|
});
|
|
791
694
|
});
|
|
792
|
-
// Endpoint to download the matterbridge plugin directory
|
|
793
695
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
794
696
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
697
|
+
if (!this.validateReq(req, res))
|
|
698
|
+
return;
|
|
795
699
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
796
700
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
797
|
-
/* istanbul ignore if */
|
|
798
701
|
if (error) {
|
|
799
702
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
800
703
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
801
704
|
}
|
|
802
705
|
});
|
|
803
706
|
});
|
|
804
|
-
// Endpoint to download the matterbridge plugin config files
|
|
805
707
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
806
708
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
709
|
+
if (!this.validateReq(req, res))
|
|
710
|
+
return;
|
|
807
711
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
808
712
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
809
|
-
/* istanbul ignore if */
|
|
810
713
|
if (error) {
|
|
811
714
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
812
715
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
813
716
|
}
|
|
814
717
|
});
|
|
815
718
|
});
|
|
816
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
817
719
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
818
720
|
this.log.debug('The frontend sent /api/download-backup');
|
|
721
|
+
if (!this.validateReq(req, res))
|
|
722
|
+
return;
|
|
819
723
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
820
|
-
/* istanbul ignore if */
|
|
821
724
|
if (error) {
|
|
822
725
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
823
726
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
824
727
|
}
|
|
825
728
|
});
|
|
826
729
|
});
|
|
827
|
-
// Endpoint to upload a package
|
|
828
730
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
731
|
+
if (!this.validateReq(req, res))
|
|
732
|
+
return;
|
|
829
733
|
const { filename } = req.body;
|
|
830
734
|
const file = req.file;
|
|
831
|
-
/* istanbul ignore if */
|
|
832
735
|
if (!file || !filename) {
|
|
833
736
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
834
737
|
res.status(400).send('Invalid request: file and filename are required');
|
|
835
738
|
return;
|
|
836
739
|
}
|
|
837
740
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
838
|
-
// Define the path where the plugin file will be saved
|
|
839
741
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
840
742
|
try {
|
|
841
|
-
// Move the uploaded file to the specified path
|
|
842
743
|
const fs = await import('node:fs');
|
|
843
744
|
await fs.promises.rename(file.path, filePath);
|
|
844
745
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
845
|
-
// Install the plugin package
|
|
846
746
|
if (filename.endsWith('.tgz')) {
|
|
847
|
-
const { spawnCommand } = await import('./
|
|
747
|
+
const { spawnCommand } = await import('./spawn.js');
|
|
848
748
|
if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
|
|
849
749
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
850
750
|
this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
|
|
@@ -870,7 +770,6 @@ export class Frontend extends EventEmitter {
|
|
|
870
770
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
871
771
|
}
|
|
872
772
|
});
|
|
873
|
-
// Fallback for routing (must be the last route)
|
|
874
773
|
this.expressApp.use((req, res) => {
|
|
875
774
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
876
775
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -881,16 +780,13 @@ export class Frontend extends EventEmitter {
|
|
|
881
780
|
async stop() {
|
|
882
781
|
this.log.debug('Stopping the frontend...');
|
|
883
782
|
const ws = await import('ws');
|
|
884
|
-
// Remove listeners from the express app
|
|
885
783
|
if (this.expressApp) {
|
|
886
784
|
this.expressApp.removeAllListeners();
|
|
887
785
|
this.expressApp = undefined;
|
|
888
786
|
this.log.debug('Frontend app closed successfully');
|
|
889
787
|
}
|
|
890
|
-
// Close the WebSocket server
|
|
891
788
|
if (this.webSocketServer) {
|
|
892
789
|
this.log.debug('Closing WebSocket server...');
|
|
893
|
-
// Close all active connections
|
|
894
790
|
this.webSocketServer.clients.forEach((client) => {
|
|
895
791
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
896
792
|
client.close();
|
|
@@ -898,9 +794,7 @@ export class Frontend extends EventEmitter {
|
|
|
898
794
|
});
|
|
899
795
|
await withTimeout(new Promise((resolve) => {
|
|
900
796
|
this.webSocketServer?.close((error) => {
|
|
901
|
-
// istanbul ignore if
|
|
902
797
|
if (error) {
|
|
903
|
-
// istanbul ignore next
|
|
904
798
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
905
799
|
}
|
|
906
800
|
else {
|
|
@@ -913,27 +807,8 @@ export class Frontend extends EventEmitter {
|
|
|
913
807
|
this.webSocketServer.removeAllListeners();
|
|
914
808
|
this.webSocketServer = undefined;
|
|
915
809
|
}
|
|
916
|
-
// Close the http server
|
|
917
810
|
if (this.httpServer) {
|
|
918
811
|
this.log.debug('Closing http server...');
|
|
919
|
-
/*
|
|
920
|
-
await withTimeout(
|
|
921
|
-
new Promise<void>((resolve) => {
|
|
922
|
-
this.httpServer?.close((error) => {
|
|
923
|
-
if (error) {
|
|
924
|
-
// istanbul ignore next
|
|
925
|
-
this.log.error(`Error closing http server: ${error}`);
|
|
926
|
-
} else {
|
|
927
|
-
this.log.debug('Http server closed successfully');
|
|
928
|
-
this.emit('server_stopped');
|
|
929
|
-
}
|
|
930
|
-
resolve();
|
|
931
|
-
});
|
|
932
|
-
}),
|
|
933
|
-
5000,
|
|
934
|
-
false,
|
|
935
|
-
);
|
|
936
|
-
*/
|
|
937
812
|
this.httpServer.close();
|
|
938
813
|
this.log.debug('Http server closed successfully');
|
|
939
814
|
this.listening = false;
|
|
@@ -942,27 +817,8 @@ export class Frontend extends EventEmitter {
|
|
|
942
817
|
this.httpServer = undefined;
|
|
943
818
|
this.log.debug('Frontend http server closed successfully');
|
|
944
819
|
}
|
|
945
|
-
// Close the https server
|
|
946
820
|
if (this.httpsServer) {
|
|
947
821
|
this.log.debug('Closing https server...');
|
|
948
|
-
/*
|
|
949
|
-
await withTimeout(
|
|
950
|
-
new Promise<void>((resolve) => {
|
|
951
|
-
this.httpsServer?.close((error) => {
|
|
952
|
-
if (error) {
|
|
953
|
-
// istanbul ignore next
|
|
954
|
-
this.log.error(`Error closing https server: ${error}`);
|
|
955
|
-
} else {
|
|
956
|
-
this.log.debug('Https server closed successfully');
|
|
957
|
-
this.emit('server_stopped');
|
|
958
|
-
}
|
|
959
|
-
resolve();
|
|
960
|
-
});
|
|
961
|
-
}),
|
|
962
|
-
5000,
|
|
963
|
-
false,
|
|
964
|
-
);
|
|
965
|
-
*/
|
|
966
822
|
this.httpsServer.close();
|
|
967
823
|
this.log.debug('Https server closed successfully');
|
|
968
824
|
this.listening = false;
|
|
@@ -973,13 +829,7 @@ export class Frontend extends EventEmitter {
|
|
|
973
829
|
}
|
|
974
830
|
this.log.debug('Frontend stopped successfully');
|
|
975
831
|
}
|
|
976
|
-
/**
|
|
977
|
-
* Retrieves the api settings data.
|
|
978
|
-
*
|
|
979
|
-
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
980
|
-
*/
|
|
981
832
|
async getApiSettings() {
|
|
982
|
-
// Update the variable system information properties
|
|
983
833
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
984
834
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
985
835
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -989,7 +839,6 @@ export class Frontend extends EventEmitter {
|
|
|
989
839
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
990
840
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
991
841
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
992
|
-
// Create the matterbridge information
|
|
993
842
|
const info = {
|
|
994
843
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
995
844
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -1025,15 +874,9 @@ export class Frontend extends EventEmitter {
|
|
|
1025
874
|
};
|
|
1026
875
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
1027
876
|
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Retrieves the reachable attribute.
|
|
1030
|
-
*
|
|
1031
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
1032
|
-
* @returns {boolean} The reachable attribute.
|
|
1033
|
-
*/
|
|
1034
877
|
getReachability(device) {
|
|
1035
878
|
if (this.matterbridge.hasCleanupStarted)
|
|
1036
|
-
return false;
|
|
879
|
+
return false;
|
|
1037
880
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1038
881
|
return false;
|
|
1039
882
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -1044,15 +887,9 @@ export class Frontend extends EventEmitter {
|
|
|
1044
887
|
return true;
|
|
1045
888
|
return false;
|
|
1046
889
|
}
|
|
1047
|
-
/**
|
|
1048
|
-
* Retrieves the power source attribute.
|
|
1049
|
-
*
|
|
1050
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1051
|
-
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1052
|
-
*/
|
|
1053
890
|
getPowerSource(endpoint) {
|
|
1054
891
|
if (this.matterbridge.hasCleanupStarted)
|
|
1055
|
-
return undefined;
|
|
892
|
+
return undefined;
|
|
1056
893
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1057
894
|
return undefined;
|
|
1058
895
|
const powerSource = (device) => {
|
|
@@ -1067,25 +904,16 @@ export class Frontend extends EventEmitter {
|
|
|
1067
904
|
}
|
|
1068
905
|
return;
|
|
1069
906
|
};
|
|
1070
|
-
// Root endpoint
|
|
1071
907
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1072
908
|
return powerSource(endpoint);
|
|
1073
|
-
// Child endpoints
|
|
1074
909
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1075
|
-
// istanbul ignore else
|
|
1076
910
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1077
911
|
return powerSource(child);
|
|
1078
912
|
}
|
|
1079
913
|
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Retrieves the battery level attribute.
|
|
1082
|
-
*
|
|
1083
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1084
|
-
* @returns {number | undefined} The battery level attribute.
|
|
1085
|
-
*/
|
|
1086
914
|
getBatteryLevel(endpoint) {
|
|
1087
915
|
if (this.matterbridge.hasCleanupStarted)
|
|
1088
|
-
return undefined;
|
|
916
|
+
return undefined;
|
|
1089
917
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1090
918
|
return undefined;
|
|
1091
919
|
const batteryLevel = (device) => {
|
|
@@ -1096,27 +924,16 @@ export class Frontend extends EventEmitter {
|
|
|
1096
924
|
}
|
|
1097
925
|
return undefined;
|
|
1098
926
|
};
|
|
1099
|
-
// Root endpoint
|
|
1100
927
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1101
928
|
return batteryLevel(endpoint);
|
|
1102
|
-
// Child endpoints
|
|
1103
929
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1104
|
-
// istanbul ignore else
|
|
1105
930
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1106
931
|
return batteryLevel(child);
|
|
1107
932
|
}
|
|
1108
933
|
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Retrieves the cluster text description from a given device.
|
|
1111
|
-
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1112
|
-
*
|
|
1113
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1114
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1115
|
-
*/
|
|
1116
934
|
getClusterTextFromDevice(device) {
|
|
1117
935
|
if (this.matterbridge.hasCleanupStarted)
|
|
1118
|
-
return '';
|
|
1119
|
-
// istanbul ignore else
|
|
936
|
+
return '';
|
|
1120
937
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1121
938
|
return '';
|
|
1122
939
|
const getUserLabel = (device) => {
|
|
@@ -1126,7 +943,6 @@ export class Frontend extends EventEmitter {
|
|
|
1126
943
|
if (composed)
|
|
1127
944
|
return 'Composed: ' + composed.value;
|
|
1128
945
|
}
|
|
1129
|
-
// istanbul ignore next cause is not reachable
|
|
1130
946
|
return '';
|
|
1131
947
|
};
|
|
1132
948
|
const getFixedLabel = (device) => {
|
|
@@ -1136,13 +952,11 @@ export class Frontend extends EventEmitter {
|
|
|
1136
952
|
if (composed)
|
|
1137
953
|
return 'Composed: ' + composed.value;
|
|
1138
954
|
}
|
|
1139
|
-
// istanbul ignore next cause is not reacheable
|
|
1140
955
|
return '';
|
|
1141
956
|
};
|
|
1142
957
|
let attributes = '';
|
|
1143
958
|
let supportedModes = [];
|
|
1144
959
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1145
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1146
960
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1147
961
|
return;
|
|
1148
962
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1232,17 +1046,11 @@ export class Frontend extends EventEmitter {
|
|
|
1232
1046
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1233
1047
|
attributes += `${getUserLabel(device)} `;
|
|
1234
1048
|
});
|
|
1235
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1236
1049
|
return attributes.trimStart().trimEnd();
|
|
1237
1050
|
}
|
|
1238
|
-
/**
|
|
1239
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1240
|
-
*
|
|
1241
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1242
|
-
*/
|
|
1243
1051
|
getPlugins() {
|
|
1244
1052
|
if (this.matterbridge.hasCleanupStarted)
|
|
1245
|
-
return [];
|
|
1053
|
+
return [];
|
|
1246
1054
|
const plugins = [];
|
|
1247
1055
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1248
1056
|
plugins.push({
|
|
@@ -1270,27 +1078,18 @@ export class Frontend extends EventEmitter {
|
|
|
1270
1078
|
schemaJson: plugin.schemaJson,
|
|
1271
1079
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1272
1080
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1273
|
-
// Childbridge mode specific data
|
|
1274
1081
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1275
1082
|
});
|
|
1276
1083
|
}
|
|
1277
1084
|
return plugins;
|
|
1278
1085
|
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Retrieves the devices from Matterbridge.
|
|
1281
|
-
*
|
|
1282
|
-
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1283
|
-
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1284
|
-
*/
|
|
1285
1086
|
getDevices(pluginName) {
|
|
1286
1087
|
if (this.matterbridge.hasCleanupStarted)
|
|
1287
|
-
return [];
|
|
1088
|
+
return [];
|
|
1288
1089
|
const devices = [];
|
|
1289
1090
|
for (const device of this.matterbridge.devices.array()) {
|
|
1290
|
-
// Filter by pluginName if provided
|
|
1291
1091
|
if (pluginName && pluginName !== device.plugin)
|
|
1292
1092
|
continue;
|
|
1293
|
-
// Check if the device has the required properties
|
|
1294
1093
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1295
1094
|
continue;
|
|
1296
1095
|
devices.push({
|
|
@@ -1311,39 +1110,24 @@ export class Frontend extends EventEmitter {
|
|
|
1311
1110
|
}
|
|
1312
1111
|
return devices;
|
|
1313
1112
|
}
|
|
1314
|
-
/**
|
|
1315
|
-
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1316
|
-
*
|
|
1317
|
-
* Response for /api/clusters
|
|
1318
|
-
*
|
|
1319
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1320
|
-
* @param {number} endpointNumber - The endpoint number.
|
|
1321
|
-
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1322
|
-
*/
|
|
1323
1113
|
getClusters(pluginName, endpointNumber) {
|
|
1324
1114
|
if (this.matterbridge.hasCleanupStarted)
|
|
1325
|
-
return;
|
|
1115
|
+
return;
|
|
1326
1116
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1327
1117
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1328
1118
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1329
1119
|
return;
|
|
1330
1120
|
}
|
|
1331
|
-
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1332
|
-
// Get the device types from the main endpoint
|
|
1333
1121
|
const deviceTypes = [];
|
|
1334
1122
|
const clusters = [];
|
|
1335
1123
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1336
1124
|
deviceTypes.push(d.deviceType);
|
|
1337
1125
|
});
|
|
1338
|
-
// Get the clusters from the main endpoint
|
|
1339
1126
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1340
1127
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1341
1128
|
return;
|
|
1342
1129
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1343
1130
|
return;
|
|
1344
|
-
// console.log(
|
|
1345
|
-
// `${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}`,
|
|
1346
|
-
// );
|
|
1347
1131
|
clusters.push({
|
|
1348
1132
|
endpoint: endpoint.number.toString(),
|
|
1349
1133
|
number: endpoint.number,
|
|
@@ -1357,19 +1141,12 @@ export class Frontend extends EventEmitter {
|
|
|
1357
1141
|
attributeLocalValue: attributeValue,
|
|
1358
1142
|
});
|
|
1359
1143
|
});
|
|
1360
|
-
// Get the child endpoints
|
|
1361
1144
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1362
|
-
// if (childEndpoints.length === 0) {
|
|
1363
|
-
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1364
|
-
// }
|
|
1365
1145
|
childEndpoints.forEach((childEndpoint) => {
|
|
1366
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1367
1146
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1368
1147
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1369
1148
|
return;
|
|
1370
1149
|
}
|
|
1371
|
-
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1372
|
-
// Get the device types of the child endpoint
|
|
1373
1150
|
const deviceTypes = [];
|
|
1374
1151
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1375
1152
|
deviceTypes.push(d.deviceType);
|
|
@@ -1379,9 +1156,6 @@ export class Frontend extends EventEmitter {
|
|
|
1379
1156
|
return;
|
|
1380
1157
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1381
1158
|
return;
|
|
1382
|
-
// console.log(
|
|
1383
|
-
// `${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}`,
|
|
1384
|
-
// );
|
|
1385
1159
|
clusters.push({
|
|
1386
1160
|
endpoint: childEndpoint.number.toString(),
|
|
1387
1161
|
number: childEndpoint.number,
|
|
@@ -1401,7 +1175,6 @@ export class Frontend extends EventEmitter {
|
|
|
1401
1175
|
async generateDiagnostic() {
|
|
1402
1176
|
this.log.debug('Generating diagnostic...');
|
|
1403
1177
|
const serverNodes = [];
|
|
1404
|
-
// istanbul ignore else
|
|
1405
1178
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1406
1179
|
if (this.matterbridge.serverNode)
|
|
1407
1180
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1412,7 +1185,6 @@ export class Frontend extends EventEmitter {
|
|
|
1412
1185
|
serverNodes.push(plugin.serverNode);
|
|
1413
1186
|
}
|
|
1414
1187
|
}
|
|
1415
|
-
// istanbul ignore next
|
|
1416
1188
|
for (const device of this.matterbridge.devices.array()) {
|
|
1417
1189
|
if (device.serverNode)
|
|
1418
1190
|
serverNodes.push(device.serverNode);
|
|
@@ -1436,15 +1208,8 @@ export class Frontend extends EventEmitter {
|
|
|
1436
1208
|
values: [...serverNodes],
|
|
1437
1209
|
})));
|
|
1438
1210
|
delete Logger.destinations.diagnostic;
|
|
1439
|
-
await wait(500);
|
|
1211
|
+
await wait(500);
|
|
1440
1212
|
}
|
|
1441
|
-
/**
|
|
1442
|
-
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1443
|
-
*
|
|
1444
|
-
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1445
|
-
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1446
|
-
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1447
|
-
*/
|
|
1448
1213
|
async wsMessageHandler(client, message) {
|
|
1449
1214
|
let data;
|
|
1450
1215
|
const sendResponse = (data) => {
|
|
@@ -1462,13 +1227,12 @@ export class Frontend extends EventEmitter {
|
|
|
1462
1227
|
client.send(JSON.stringify(data));
|
|
1463
1228
|
}
|
|
1464
1229
|
else {
|
|
1465
|
-
// istanbul ignore next cause is only a safety check
|
|
1466
1230
|
this.log.error('Cannot send api response, client not connected');
|
|
1467
1231
|
}
|
|
1468
1232
|
};
|
|
1469
1233
|
try {
|
|
1470
1234
|
data = JSON.parse(message.toString());
|
|
1471
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1235
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1472
1236
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1473
1237
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1474
1238
|
return;
|
|
@@ -1525,22 +1289,7 @@ export class Frontend extends EventEmitter {
|
|
|
1525
1289
|
}
|
|
1526
1290
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1527
1291
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1528
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1529
|
-
/*
|
|
1530
|
-
const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
|
|
1531
|
-
if (plugin) {
|
|
1532
|
-
this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
|
|
1533
|
-
await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
|
|
1534
|
-
this.wssSendRestartRequired();
|
|
1535
|
-
this.wssSendRefreshRequired('plugins');
|
|
1536
|
-
this.wssSendRefreshRequired('devices');
|
|
1537
|
-
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1538
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1539
|
-
} else {
|
|
1540
|
-
this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
|
|
1541
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
1542
|
-
}
|
|
1543
|
-
*/
|
|
1292
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1544
1293
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1545
1294
|
if (plugin) {
|
|
1546
1295
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1553,7 +1302,7 @@ export class Frontend extends EventEmitter {
|
|
|
1553
1302
|
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1554
1303
|
return;
|
|
1555
1304
|
})
|
|
1556
|
-
.catch(
|
|
1305
|
+
.catch((_error) => { });
|
|
1557
1306
|
}
|
|
1558
1307
|
else {
|
|
1559
1308
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
@@ -1567,10 +1316,6 @@ export class Frontend extends EventEmitter {
|
|
|
1567
1316
|
}
|
|
1568
1317
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1569
1318
|
this.log.debug(`Removing plugin ${data.params.pluginName}...`);
|
|
1570
|
-
/*
|
|
1571
|
-
await this.server.fetch({ type: 'plugins_shutdown', src: this.server.name, dst: 'plugins', params: { plugin: data.params.pluginName, reason: 'The plugin has been removed.', removeAllDevices: true } }, 5000);
|
|
1572
|
-
await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
|
|
1573
|
-
*/
|
|
1574
1319
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1575
1320
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1576
1321
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1598,10 +1343,8 @@ export class Frontend extends EventEmitter {
|
|
|
1598
1343
|
this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
|
|
1599
1344
|
setImmediate(async () => {
|
|
1600
1345
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
1601
|
-
// @ts-expect-error Accessing private method
|
|
1602
1346
|
if (plugin.serverNode)
|
|
1603
1347
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1604
|
-
// @ts-expect-error Accessing private method
|
|
1605
1348
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1606
1349
|
await this.matterbridge.startServerNode(device.serverNode);
|
|
1607
1350
|
this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
|
|
@@ -1617,11 +1360,9 @@ export class Frontend extends EventEmitter {
|
|
|
1617
1360
|
}
|
|
1618
1361
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1619
1362
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
|
|
1620
|
-
// @ts-expect-error Accessing private method
|
|
1621
1363
|
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1622
1364
|
device.serverNode = undefined;
|
|
1623
1365
|
}
|
|
1624
|
-
// @ts-expect-error Accessing private method
|
|
1625
1366
|
if (plugin.serverNode)
|
|
1626
1367
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1627
1368
|
plugin.serverNode = undefined;
|
|
@@ -1640,37 +1381,30 @@ export class Frontend extends EventEmitter {
|
|
|
1640
1381
|
this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
|
|
1641
1382
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1642
1383
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1643
|
-
// Stop server nodes
|
|
1644
1384
|
if (plugin.serverNode) {
|
|
1645
|
-
// @ts-expect-error Accessing private method
|
|
1646
1385
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1647
1386
|
plugin.serverNode = undefined;
|
|
1648
1387
|
}
|
|
1649
1388
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
|
|
1650
|
-
// @ts-expect-error Accessing private method
|
|
1651
1389
|
if (device.serverNode)
|
|
1652
1390
|
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1653
1391
|
device.serverNode = undefined;
|
|
1654
1392
|
this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
|
|
1655
1393
|
this.matterbridge.devices.remove(device);
|
|
1656
1394
|
}
|
|
1657
|
-
// @ts-expect-error Accessing private method
|
|
1658
1395
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1659
1396
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1660
1397
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1661
|
-
plugin.restartRequired = false;
|
|
1398
|
+
plugin.restartRequired = false;
|
|
1662
1399
|
let needRestart = 0;
|
|
1663
1400
|
for (const plugin of this.matterbridge.plugins) {
|
|
1664
1401
|
if (plugin.restartRequired)
|
|
1665
1402
|
needRestart++;
|
|
1666
1403
|
}
|
|
1667
1404
|
if (needRestart === 0)
|
|
1668
|
-
this.wssSendRestartNotRequired(true);
|
|
1669
|
-
// Start server nodes
|
|
1670
|
-
// @ts-expect-error Accessing private method
|
|
1405
|
+
this.wssSendRestartNotRequired(true);
|
|
1671
1406
|
if (plugin.serverNode)
|
|
1672
1407
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1673
|
-
// @ts-expect-error Accessing private method
|
|
1674
1408
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1675
1409
|
await this.matterbridge.startServerNode(device.serverNode);
|
|
1676
1410
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1815,9 +1549,6 @@ export class Frontend extends EventEmitter {
|
|
|
1815
1549
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1816
1550
|
}
|
|
1817
1551
|
if (data.params.advertise) {
|
|
1818
|
-
// TODO: matter.js 0.16.0
|
|
1819
|
-
// await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
|
|
1820
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1821
1552
|
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1822
1553
|
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1823
1554
|
await advertiser.advertise(true);
|
|
@@ -1881,7 +1612,6 @@ export class Frontend extends EventEmitter {
|
|
|
1881
1612
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
|
|
1882
1613
|
return;
|
|
1883
1614
|
}
|
|
1884
|
-
// istanbul ignore next
|
|
1885
1615
|
const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1886
1616
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
|
|
1887
1617
|
}
|
|
@@ -1895,7 +1625,6 @@ export class Frontend extends EventEmitter {
|
|
|
1895
1625
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
|
|
1896
1626
|
return;
|
|
1897
1627
|
}
|
|
1898
|
-
// istanbul ignore next
|
|
1899
1628
|
const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1900
1629
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
|
|
1901
1630
|
}
|
|
@@ -1947,22 +1676,22 @@ export class Frontend extends EventEmitter {
|
|
|
1947
1676
|
if (isValidString(data.params.value, 4)) {
|
|
1948
1677
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1949
1678
|
if (data.params.value === 'Debug') {
|
|
1950
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1679
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1951
1680
|
}
|
|
1952
1681
|
else if (data.params.value === 'Info') {
|
|
1953
|
-
await this.matterbridge.setLogLevel("info"
|
|
1682
|
+
await this.matterbridge.setLogLevel("info");
|
|
1954
1683
|
}
|
|
1955
1684
|
else if (data.params.value === 'Notice') {
|
|
1956
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1685
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1957
1686
|
}
|
|
1958
1687
|
else if (data.params.value === 'Warn') {
|
|
1959
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1688
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1960
1689
|
}
|
|
1961
1690
|
else if (data.params.value === 'Error') {
|
|
1962
|
-
await this.matterbridge.setLogLevel("error"
|
|
1691
|
+
await this.matterbridge.setLogLevel("error");
|
|
1963
1692
|
}
|
|
1964
1693
|
else if (data.params.value === 'Fatal') {
|
|
1965
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1694
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1966
1695
|
}
|
|
1967
1696
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1968
1697
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1973,7 +1702,6 @@ export class Frontend extends EventEmitter {
|
|
|
1973
1702
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1974
1703
|
this.matterbridge.fileLogger = data.params.value;
|
|
1975
1704
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1976
|
-
// Create the file logger for matterbridge
|
|
1977
1705
|
if (data.params.value)
|
|
1978
1706
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1979
1707
|
else
|
|
@@ -2003,12 +1731,11 @@ export class Frontend extends EventEmitter {
|
|
|
2003
1731
|
Logger.level = MatterLogLevel.FATAL;
|
|
2004
1732
|
}
|
|
2005
1733
|
this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1734
|
+
let callbackLogLevel = "notice";
|
|
1735
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1736
|
+
callbackLogLevel = "info";
|
|
1737
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1738
|
+
callbackLogLevel = "debug";
|
|
2012
1739
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
2013
1740
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
2014
1741
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -2060,7 +1787,6 @@ export class Frontend extends EventEmitter {
|
|
|
2060
1787
|
}
|
|
2061
1788
|
break;
|
|
2062
1789
|
case 'setmatterport':
|
|
2063
|
-
// eslint-disable-next-line no-case-declarations
|
|
2064
1790
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2065
1791
|
if (isValidNumber(port, 5540, 5600)) {
|
|
2066
1792
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -2080,7 +1806,6 @@ export class Frontend extends EventEmitter {
|
|
|
2080
1806
|
}
|
|
2081
1807
|
break;
|
|
2082
1808
|
case 'setmatterdiscriminator':
|
|
2083
|
-
// eslint-disable-next-line no-case-declarations
|
|
2084
1809
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2085
1810
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
2086
1811
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -2100,7 +1825,6 @@ export class Frontend extends EventEmitter {
|
|
|
2100
1825
|
}
|
|
2101
1826
|
break;
|
|
2102
1827
|
case 'setmatterpasscode':
|
|
2103
|
-
// eslint-disable-next-line no-case-declarations
|
|
2104
1828
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2105
1829
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
2106
1830
|
this.matterbridge.passcode = passcode;
|
|
@@ -2146,19 +1870,15 @@ export class Frontend extends EventEmitter {
|
|
|
2146
1870
|
return;
|
|
2147
1871
|
}
|
|
2148
1872
|
const config = plugin.configJson;
|
|
2149
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2150
1873
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2151
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2152
1874
|
if (select === 'serial')
|
|
2153
1875
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
2154
1876
|
if (select === 'name')
|
|
2155
1877
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
2156
1878
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2157
|
-
// Remove postfix from the serial if it exists
|
|
2158
1879
|
if (config.postfix) {
|
|
2159
1880
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2160
1881
|
}
|
|
2161
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
2162
1882
|
if (isValidArray(config.whiteList, 1)) {
|
|
2163
1883
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
2164
1884
|
config.whiteList.push(data.params.serial);
|
|
@@ -2167,7 +1887,6 @@ export class Frontend extends EventEmitter {
|
|
|
2167
1887
|
config.whiteList.push(data.params.name);
|
|
2168
1888
|
}
|
|
2169
1889
|
}
|
|
2170
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
2171
1890
|
if (isValidArray(config.blackList, 1)) {
|
|
2172
1891
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
2173
1892
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2195,9 +1914,7 @@ export class Frontend extends EventEmitter {
|
|
|
2195
1914
|
return;
|
|
2196
1915
|
}
|
|
2197
1916
|
const config = plugin.configJson;
|
|
2198
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2199
1917
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2200
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2201
1918
|
if (select === 'serial')
|
|
2202
1919
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2203
1920
|
if (select === 'name')
|
|
@@ -2206,7 +1923,6 @@ export class Frontend extends EventEmitter {
|
|
|
2206
1923
|
if (config.postfix) {
|
|
2207
1924
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2208
1925
|
}
|
|
2209
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2210
1926
|
if (isValidArray(config.whiteList, 1)) {
|
|
2211
1927
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2212
1928
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2215,7 +1931,6 @@ export class Frontend extends EventEmitter {
|
|
|
2215
1931
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2216
1932
|
}
|
|
2217
1933
|
}
|
|
2218
|
-
// Add the serial to the blackList
|
|
2219
1934
|
if (isValidArray(config.blackList)) {
|
|
2220
1935
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2221
1936
|
config.blackList.push(data.params.serial);
|
|
@@ -2238,7 +1953,6 @@ export class Frontend extends EventEmitter {
|
|
|
2238
1953
|
}
|
|
2239
1954
|
}
|
|
2240
1955
|
else {
|
|
2241
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2242
1956
|
const localData = data;
|
|
2243
1957
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2244
1958
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2248,46 +1962,23 @@ export class Frontend extends EventEmitter {
|
|
|
2248
1962
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2249
1963
|
}
|
|
2250
1964
|
}
|
|
2251
|
-
/**
|
|
2252
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2253
|
-
*
|
|
2254
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2255
|
-
* @param {string} time - The time string of the message
|
|
2256
|
-
* @param {string} name - The logger name of the message
|
|
2257
|
-
* @param {string} message - The content of the message.
|
|
2258
|
-
*
|
|
2259
|
-
* @remarks
|
|
2260
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2261
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2262
|
-
* The function sends the message to all connected clients.
|
|
2263
|
-
*/
|
|
2264
1965
|
wssSendLogMessage(level, time, name, message) {
|
|
2265
1966
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2266
1967
|
return;
|
|
2267
1968
|
if (!level || !time || !name || !message)
|
|
2268
1969
|
return;
|
|
2269
|
-
// Remove ANSI escape codes from the message
|
|
2270
|
-
// eslint-disable-next-line no-control-regex
|
|
2271
1970
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2272
|
-
// Remove leading asterisks from the message
|
|
2273
1971
|
message = message.replace(/^\*+/, '');
|
|
2274
|
-
// Replace all occurrences of \t and \n
|
|
2275
1972
|
message = message.replace(/[\t\n]/g, '');
|
|
2276
|
-
// Remove non-printable characters
|
|
2277
|
-
// eslint-disable-next-line no-control-regex
|
|
2278
1973
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2279
|
-
// Replace all occurrences of \" with "
|
|
2280
1974
|
message = message.replace(/\\"/g, '"');
|
|
2281
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2282
1975
|
const maxContinuousLength = 100;
|
|
2283
1976
|
const keepStartLength = 20;
|
|
2284
1977
|
const keepEndLength = 20;
|
|
2285
|
-
// Split the message into words
|
|
2286
1978
|
if (level !== 'spawn') {
|
|
2287
1979
|
message = message
|
|
2288
1980
|
.split(' ')
|
|
2289
1981
|
.map((word) => {
|
|
2290
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2291
1982
|
if (word.length > maxContinuousLength) {
|
|
2292
1983
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2293
1984
|
}
|
|
@@ -2295,34 +1986,14 @@ export class Frontend extends EventEmitter {
|
|
|
2295
1986
|
})
|
|
2296
1987
|
.join(' ');
|
|
2297
1988
|
}
|
|
2298
|
-
// Send the message to all connected clients
|
|
2299
1989
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2300
1990
|
}
|
|
2301
|
-
/**
|
|
2302
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2303
|
-
*
|
|
2304
|
-
* @param {string} changed - The changed value.
|
|
2305
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2306
|
-
* possible values for changed:
|
|
2307
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2308
|
-
* - 'plugins'
|
|
2309
|
-
* - 'devices'
|
|
2310
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2311
|
-
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2312
|
-
*/
|
|
2313
1991
|
wssSendRefreshRequired(changed, params) {
|
|
2314
1992
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2315
1993
|
return;
|
|
2316
1994
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2317
|
-
// Send the message to all connected clients
|
|
2318
1995
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2319
1996
|
}
|
|
2320
|
-
/**
|
|
2321
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2322
|
-
*
|
|
2323
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2324
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2325
|
-
*/
|
|
2326
1997
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2327
1998
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2328
1999
|
return;
|
|
@@ -2331,14 +2002,8 @@ export class Frontend extends EventEmitter {
|
|
|
2331
2002
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2332
2003
|
if (snackbar === true)
|
|
2333
2004
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2334
|
-
// Send the message to all connected clients
|
|
2335
2005
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2336
2006
|
}
|
|
2337
|
-
/**
|
|
2338
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2339
|
-
*
|
|
2340
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2341
|
-
*/
|
|
2342
2007
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2343
2008
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2344
2009
|
return;
|
|
@@ -2346,145 +2011,64 @@ export class Frontend extends EventEmitter {
|
|
|
2346
2011
|
this.matterbridge.restartRequired = false;
|
|
2347
2012
|
if (snackbar === true)
|
|
2348
2013
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2349
|
-
// Send the message to all connected clients
|
|
2350
2014
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2351
2015
|
}
|
|
2352
|
-
/**
|
|
2353
|
-
* Sends a need to update WebSocket message to all connected clients.
|
|
2354
|
-
*
|
|
2355
|
-
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2356
|
-
*/
|
|
2357
2016
|
wssSendUpdateRequired(devVersion = false) {
|
|
2358
2017
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2359
2018
|
return;
|
|
2360
2019
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2361
2020
|
this.matterbridge.updateRequired = true;
|
|
2362
|
-
// Send the message to all connected clients
|
|
2363
2021
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2364
2022
|
}
|
|
2365
|
-
/**
|
|
2366
|
-
* Sends a cpu update message to all connected clients.
|
|
2367
|
-
*
|
|
2368
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2369
|
-
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2370
|
-
*/
|
|
2371
2023
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2372
2024
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2373
2025
|
return;
|
|
2374
|
-
// istanbul ignore else
|
|
2375
2026
|
if (hasParameter('debug'))
|
|
2376
2027
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2377
|
-
// Send the message to all connected clients
|
|
2378
2028
|
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 } });
|
|
2379
2029
|
}
|
|
2380
|
-
/**
|
|
2381
|
-
* Sends a memory update message to all connected clients.
|
|
2382
|
-
*
|
|
2383
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2384
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2385
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2386
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2387
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2388
|
-
* @param {string} external - The external memory in bytes.
|
|
2389
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2390
|
-
*/
|
|
2391
2030
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2392
2031
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2393
2032
|
return;
|
|
2394
|
-
// istanbul ignore else
|
|
2395
2033
|
if (hasParameter('debug'))
|
|
2396
2034
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2397
|
-
// Send the message to all connected clients
|
|
2398
2035
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2399
2036
|
}
|
|
2400
|
-
/**
|
|
2401
|
-
* Sends an uptime update message to all connected clients.
|
|
2402
|
-
*
|
|
2403
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2404
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2405
|
-
*/
|
|
2406
2037
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2407
2038
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2408
2039
|
return;
|
|
2409
|
-
// istanbul ignore else
|
|
2410
2040
|
if (hasParameter('debug'))
|
|
2411
2041
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2412
|
-
// Send the message to all connected clients
|
|
2413
2042
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2414
2043
|
}
|
|
2415
|
-
/**
|
|
2416
|
-
* Sends an open snackbar message to all connected clients.
|
|
2417
|
-
*
|
|
2418
|
-
* @param {string} message - The message to send.
|
|
2419
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2420
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2421
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2422
|
-
*
|
|
2423
|
-
* @remarks
|
|
2424
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2425
|
-
*/
|
|
2426
2044
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2427
2045
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2428
2046
|
return;
|
|
2429
2047
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2430
|
-
// Send the message to all connected clients
|
|
2431
2048
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2432
2049
|
}
|
|
2433
|
-
/**
|
|
2434
|
-
* Sends a close snackbar message to all connected clients.
|
|
2435
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2436
|
-
*
|
|
2437
|
-
* @param {string} message - The message to send.
|
|
2438
|
-
*/
|
|
2439
2050
|
wssSendCloseSnackbarMessage(message) {
|
|
2440
2051
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2441
2052
|
return;
|
|
2442
2053
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2443
|
-
// Send the message to all connected clients
|
|
2444
2054
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2445
2055
|
}
|
|
2446
|
-
/**
|
|
2447
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2448
|
-
*
|
|
2449
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2450
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2451
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2452
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2453
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2454
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2455
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2456
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2457
|
-
*
|
|
2458
|
-
* @remarks
|
|
2459
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2460
|
-
* with the updated attribute information.
|
|
2461
|
-
*/
|
|
2462
2056
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2463
2057
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2464
2058
|
return;
|
|
2465
2059
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2466
|
-
// Send the message to all connected clients
|
|
2467
2060
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2468
2061
|
}
|
|
2469
|
-
/**
|
|
2470
|
-
* Sends a message to all connected clients.
|
|
2471
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2472
|
-
*
|
|
2473
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2474
|
-
*/
|
|
2475
2062
|
wssBroadcastMessage(msg) {
|
|
2476
2063
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2477
2064
|
return;
|
|
2478
|
-
// Send the message to all connected clients
|
|
2479
2065
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2480
2066
|
if (msg.method !== 'log')
|
|
2481
2067
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
2482
2068
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2483
|
-
// istanbul ignore else
|
|
2484
2069
|
if (client.readyState === client.OPEN) {
|
|
2485
2070
|
client.send(stringifiedMsg);
|
|
2486
2071
|
}
|
|
2487
2072
|
});
|
|
2488
2073
|
}
|
|
2489
2074
|
}
|
|
2490
|
-
//# sourceMappingURL=frontend.js.map
|