matterbridge 3.5.0-dev-20260117-88ddbe4 → 3.5.0
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 +130 -122
- package/README.md +14 -14
- package/dist/broadcastServer.d.ts +115 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +117 -0
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +43 -0
- package/dist/broadcastServerTypes.d.ts.map +1 -0
- package/dist/broadcastServerTypes.js +24 -0
- package/dist/broadcastServerTypes.js.map +1 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +98 -1
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +36 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +37 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +42 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +38 -0
- package/dist/cliHistory.js.map +1 -0
- package/dist/clusters/export.d.ts +1 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/deviceManager.d.ts +108 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +113 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +75 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +43 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +55 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +56 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +55 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +57 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +1 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +41 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +43 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +43 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +58 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +64 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +77 -1
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +82 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +100 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +83 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +36 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +79 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +21 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +24 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +74 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +171 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +99 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +23 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +23 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -24
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +187 -4
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +371 -139
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +49 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +53 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +60 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +187 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +543 -73
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +57 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.js.map +1 -0
- package/dist/helpers.d.ts +43 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/jestutils/export.d.ts +1 -0
- package/dist/jestutils/export.d.ts.map +1 -0
- package/dist/jestutils/export.js +1 -0
- package/dist/jestutils/export.js.map +1 -0
- package/dist/jestutils/jestHelpers.d.ts +255 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +372 -14
- package/dist/jestutils/jestHelpers.js.map +1 -0
- package/dist/logger/export.d.ts +1 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +1 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +1 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +1 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +1 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +1 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +1 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterNode.d.ts +258 -0
- package/dist/matterNode.d.ts.map +1 -0
- package/dist/matterNode.js +362 -9
- package/dist/matterNode.js.map +1 -0
- package/dist/matterbridge.d.ts +362 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +879 -56
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +36 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +38 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +24 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +649 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +673 -6
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +36 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +38 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1332 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1457 -53
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +425 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +483 -20
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgeEndpointTypes.d.ts +70 -0
- package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
- package/dist/matterbridgeEndpointTypes.js +25 -0
- package/dist/matterbridgeEndpointTypes.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +425 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +451 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +46 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +26 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +305 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +341 -5
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +157 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +178 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +1 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +93 -1
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +77 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +60 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +60 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -0
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +32 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +42 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +42 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +1 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/format.d.ts +49 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +49 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hex.d.ts +85 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/inspector.d.ts +63 -0
- package/dist/utils/inspector.d.ts.map +1 -0
- package/dist/utils/inspector.js +69 -1
- package/dist/utils/inspector.js.map +1 -0
- package/dist/utils/isValid.d.ts +93 -0
- package/dist/utils/isValid.d.ts.map +1 -0
- package/dist/utils/isValid.js +93 -0
- package/dist/utils/isValid.js.map +1 -0
- package/dist/utils/network.d.ts +116 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +126 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +32 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +71 -1
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/tracker.d.ts +56 -0
- package/dist/utils/tracker.d.ts.map +1 -0
- package/dist/utils/tracker.js +64 -1
- package/dist/utils/tracker.js.map +1 -0
- package/dist/utils/wait.d.ts +51 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/dist/workerGlobalPrefix.d.ts +24 -0
- package/dist/workerGlobalPrefix.d.ts.map +1 -0
- package/dist/workerGlobalPrefix.js +37 -5
- package/dist/workerGlobalPrefix.js.map +1 -0
- package/dist/workerTypes.d.ts +25 -0
- package/dist/workerTypes.d.ts.map +1 -0
- package/dist/workerTypes.js +24 -0
- package/dist/workerTypes.js.map +1 -0
- package/dist/workers.d.ts +61 -0
- package/dist/workers.d.ts.map +1 -0
- package/dist/workers.js +68 -4
- package/dist/workers.js.map +1 -0
- package/frontend/build/assets/index.js +4 -4
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +5 -35
- package/package.json +7 -7
package/dist/frontend.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
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 */
|
|
1
25
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
2
26
|
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
27
|
+
// Node modules
|
|
3
28
|
import os from 'node:os';
|
|
4
29
|
import path from 'node:path';
|
|
5
30
|
import EventEmitter from 'node:events';
|
|
31
|
+
// AnsiLogger module
|
|
6
32
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
7
33
|
import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
|
|
8
34
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
|
|
@@ -13,7 +39,7 @@ import { PowerSource } from '@matter/types/clusters/power-source';
|
|
|
13
39
|
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
|
|
14
40
|
import { isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/isValid.js';
|
|
15
41
|
import { createZip } from './utils/createZip.js';
|
|
16
|
-
import { hasParameter } from './utils/commandLine.js';
|
|
42
|
+
import { hasParameter, getParameter } from './utils/commandLine.js';
|
|
17
43
|
import { withTimeout, wait } from './utils/wait.js';
|
|
18
44
|
import { inspectError } from './utils/error.js';
|
|
19
45
|
import { formatBytes, formatUptime, formatPercent } from './utils/format.js';
|
|
@@ -37,7 +63,7 @@ export class Frontend extends EventEmitter {
|
|
|
37
63
|
constructor(matterbridge) {
|
|
38
64
|
super();
|
|
39
65
|
this.matterbridge = matterbridge;
|
|
40
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
66
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
41
67
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
42
68
|
this.server = new BroadcastServer('frontend', this.log);
|
|
43
69
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -48,6 +74,7 @@ export class Frontend extends EventEmitter {
|
|
|
48
74
|
}
|
|
49
75
|
async msgHandler(msg) {
|
|
50
76
|
if (this.server.isWorkerRequest(msg)) {
|
|
77
|
+
// istanbul ignore else
|
|
51
78
|
if (this.verbose)
|
|
52
79
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
53
80
|
switch (msg.type) {
|
|
@@ -99,11 +126,13 @@ export class Frontend extends EventEmitter {
|
|
|
99
126
|
this.server.respond({ ...msg, result: { success: true } });
|
|
100
127
|
break;
|
|
101
128
|
default:
|
|
129
|
+
// istanbul ignore next
|
|
102
130
|
if (this.verbose)
|
|
103
131
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
134
|
if (this.server.isWorkerResponse(msg) && msg.result) {
|
|
135
|
+
// istanbul ignore next
|
|
107
136
|
if (this.verbose)
|
|
108
137
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
109
138
|
switch (msg.type) {
|
|
@@ -139,23 +168,55 @@ export class Frontend extends EventEmitter {
|
|
|
139
168
|
this.port = port;
|
|
140
169
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
141
170
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
171
|
+
// Initialize multer with the upload directory
|
|
142
172
|
const multer = await import('multer');
|
|
143
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
173
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
144
174
|
const upload = multer.default({ dest: uploadDir });
|
|
175
|
+
// Create the express app that serves the frontend
|
|
145
176
|
const express = await import('express');
|
|
146
177
|
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
|
|
147
204
|
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
|
|
148
206
|
this.log.debug(`Creating WebSocketServer...`);
|
|
149
207
|
const ws = await import('ws');
|
|
150
208
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
151
209
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
152
210
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
153
211
|
const clientIp = request.socket.remoteAddress;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (this.matterbridge.getLogLevel() === "
|
|
158
|
-
callbackLogLevel = "
|
|
212
|
+
// Set the global logger callback for the WebSocketServer
|
|
213
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
214
|
+
// istanbul ignore else
|
|
215
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
216
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
217
|
+
// istanbul ignore else
|
|
218
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
219
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
159
220
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
160
221
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
161
222
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -171,22 +232,33 @@ export class Frontend extends EventEmitter {
|
|
|
171
232
|
});
|
|
172
233
|
ws.on('close', () => {
|
|
173
234
|
this.log.info('WebSocket client disconnected');
|
|
235
|
+
// istanbul ignore else
|
|
174
236
|
if (this.webSocketServer?.clients.size === 0) {
|
|
175
237
|
AnsiLogger.setGlobalCallback(undefined);
|
|
176
238
|
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
177
239
|
}
|
|
178
240
|
});
|
|
241
|
+
// istanbul ignore next
|
|
179
242
|
ws.on('error', (error) => {
|
|
243
|
+
// istanbul ignore next
|
|
180
244
|
this.log.error(`WebSocket client error: ${error}`);
|
|
181
245
|
});
|
|
182
246
|
});
|
|
183
247
|
this.webSocketServer.on('close', () => {
|
|
184
248
|
this.log.debug(`WebSocketServer closed`);
|
|
185
249
|
});
|
|
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
|
|
186
257
|
this.webSocketServer.on('error', (ws, error) => {
|
|
187
258
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
188
259
|
});
|
|
189
260
|
if (!hasParameter('ssl')) {
|
|
261
|
+
// Create an HTTP server and attach the express app
|
|
190
262
|
const http = await import('node:http');
|
|
191
263
|
try {
|
|
192
264
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -197,7 +269,9 @@ export class Frontend extends EventEmitter {
|
|
|
197
269
|
this.emit('server_error', error);
|
|
198
270
|
return;
|
|
199
271
|
}
|
|
272
|
+
// Listen on the specified port
|
|
200
273
|
if (hasParameter('ingress')) {
|
|
274
|
+
// We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
|
|
201
275
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
202
276
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
203
277
|
this.listening = true;
|
|
@@ -205,14 +279,18 @@ export class Frontend extends EventEmitter {
|
|
|
205
279
|
});
|
|
206
280
|
}
|
|
207
281
|
else {
|
|
208
|
-
|
|
282
|
+
// We listen to all available addresses
|
|
283
|
+
this.httpServer.listen(this.port, getParameter('bind'), () => {
|
|
209
284
|
const addr = this.httpServer?.address();
|
|
285
|
+
// istanbul ignore else
|
|
210
286
|
if (addr && typeof addr !== 'string') {
|
|
211
287
|
this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
212
288
|
}
|
|
213
|
-
|
|
289
|
+
// istanbul ignore else
|
|
290
|
+
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
214
291
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
215
|
-
|
|
292
|
+
// istanbul ignore else
|
|
293
|
+
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
216
294
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
217
295
|
this.listening = true;
|
|
218
296
|
this.emit('server_listening', 'http', this.port);
|
|
@@ -220,24 +298,30 @@ export class Frontend extends EventEmitter {
|
|
|
220
298
|
}
|
|
221
299
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
222
300
|
try {
|
|
301
|
+
// Only proceed for real WebSocket upgrades
|
|
302
|
+
// istanbul ignore next cause is only a safety check
|
|
223
303
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
224
304
|
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
225
305
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
226
306
|
return socket.destroy();
|
|
227
307
|
}
|
|
308
|
+
// Build a URL so we can read ?password=...
|
|
228
309
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
310
|
+
// Validate WebSocket password
|
|
229
311
|
const password = url.searchParams.get('password') ?? '';
|
|
230
312
|
if (password !== this.storedPassword) {
|
|
231
313
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
232
314
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
233
315
|
return socket.destroy();
|
|
234
316
|
}
|
|
317
|
+
// Complete the WebSocket handshake
|
|
235
318
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
236
319
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
237
320
|
this.webSocketServer?.emit('connection', ws, req);
|
|
238
321
|
});
|
|
239
322
|
}
|
|
240
323
|
catch (err) {
|
|
324
|
+
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
241
325
|
{
|
|
242
326
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
243
327
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -260,6 +344,7 @@ export class Frontend extends EventEmitter {
|
|
|
260
344
|
});
|
|
261
345
|
}
|
|
262
346
|
else {
|
|
347
|
+
// SSL is enabled, load the certificate and the private key
|
|
263
348
|
let cert;
|
|
264
349
|
let key;
|
|
265
350
|
let ca;
|
|
@@ -269,6 +354,7 @@ export class Frontend extends EventEmitter {
|
|
|
269
354
|
let httpsServerOptions = {};
|
|
270
355
|
const fs = await import('node:fs');
|
|
271
356
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
357
|
+
// Load the p12 certificate and the passphrase
|
|
272
358
|
try {
|
|
273
359
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
274
360
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -280,7 +366,7 @@ export class Frontend extends EventEmitter {
|
|
|
280
366
|
}
|
|
281
367
|
try {
|
|
282
368
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
283
|
-
passphrase = passphrase.trim();
|
|
369
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
284
370
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
285
371
|
}
|
|
286
372
|
catch (error) {
|
|
@@ -291,6 +377,7 @@ export class Frontend extends EventEmitter {
|
|
|
291
377
|
httpsServerOptions = { pfx, passphrase };
|
|
292
378
|
}
|
|
293
379
|
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.
|
|
294
381
|
try {
|
|
295
382
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
296
383
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -320,9 +407,10 @@ export class Frontend extends EventEmitter {
|
|
|
320
407
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
321
408
|
}
|
|
322
409
|
if (hasParameter('mtls')) {
|
|
323
|
-
httpsServerOptions.requestCert = true;
|
|
324
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
410
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
411
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
325
412
|
}
|
|
413
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
326
414
|
const https = await import('node:https');
|
|
327
415
|
try {
|
|
328
416
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -333,7 +421,9 @@ export class Frontend extends EventEmitter {
|
|
|
333
421
|
this.emit('server_error', error);
|
|
334
422
|
return;
|
|
335
423
|
}
|
|
424
|
+
// Listen on the specified port
|
|
336
425
|
if (hasParameter('ingress')) {
|
|
426
|
+
// We limit to all ipv4 addresses when running in ingress mode (Home Assistant add-on)
|
|
337
427
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
338
428
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
339
429
|
this.listening = true;
|
|
@@ -341,14 +431,18 @@ export class Frontend extends EventEmitter {
|
|
|
341
431
|
});
|
|
342
432
|
}
|
|
343
433
|
else {
|
|
344
|
-
|
|
434
|
+
// We listen to all available addresses
|
|
435
|
+
this.httpsServer.listen(this.port, getParameter('bind'), () => {
|
|
345
436
|
const addr = this.httpsServer?.address();
|
|
437
|
+
// istanbul ignore else
|
|
346
438
|
if (addr && typeof addr !== 'string') {
|
|
347
439
|
this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
348
440
|
}
|
|
349
|
-
|
|
441
|
+
// istanbul ignore else
|
|
442
|
+
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
350
443
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
351
|
-
|
|
444
|
+
// istanbul ignore else
|
|
445
|
+
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
352
446
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
353
447
|
this.listening = true;
|
|
354
448
|
this.emit('server_listening', 'https', this.port);
|
|
@@ -356,23 +450,29 @@ export class Frontend extends EventEmitter {
|
|
|
356
450
|
}
|
|
357
451
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
358
452
|
try {
|
|
453
|
+
// Only proceed for real WebSocket upgrades
|
|
454
|
+
// istanbul ignore next cause is only a safety check
|
|
359
455
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
360
456
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
361
457
|
return socket.destroy();
|
|
362
458
|
}
|
|
459
|
+
// Build a URL so we can read ?password=...
|
|
363
460
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
461
|
+
// Validate WebSocket password
|
|
364
462
|
const password = url.searchParams.get('password') ?? '';
|
|
365
463
|
if (password !== this.storedPassword) {
|
|
366
464
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
367
465
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
368
466
|
return socket.destroy();
|
|
369
467
|
}
|
|
468
|
+
// Complete the WebSocket handshake
|
|
370
469
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
371
470
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
372
471
|
this.webSocketServer?.emit('connection', ws, req);
|
|
373
472
|
});
|
|
374
473
|
}
|
|
375
474
|
catch (err) {
|
|
475
|
+
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
376
476
|
{
|
|
377
477
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
378
478
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -394,6 +494,7 @@ export class Frontend extends EventEmitter {
|
|
|
394
494
|
return;
|
|
395
495
|
});
|
|
396
496
|
}
|
|
497
|
+
// Subscribe to cli events
|
|
397
498
|
cliEmitter.removeAllListeners();
|
|
398
499
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
399
500
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -404,6 +505,8 @@ export class Frontend extends EventEmitter {
|
|
|
404
505
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
405
506
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
406
507
|
});
|
|
508
|
+
// Endpoint to validate login code
|
|
509
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
407
510
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
408
511
|
const { password } = req.body;
|
|
409
512
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -416,17 +519,20 @@ export class Frontend extends EventEmitter {
|
|
|
416
519
|
res.json({ valid: false });
|
|
417
520
|
}
|
|
418
521
|
});
|
|
522
|
+
// Endpoint to provide health check for docker
|
|
419
523
|
this.expressApp.get('/health', (req, res) => {
|
|
420
524
|
this.log.debug('Express received /health');
|
|
421
525
|
const healthStatus = {
|
|
422
|
-
status: 'ok',
|
|
423
|
-
uptime: process.uptime(),
|
|
424
|
-
timestamp: new Date().toISOString(),
|
|
526
|
+
status: 'ok', // Indicate service is healthy
|
|
527
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
528
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
425
529
|
};
|
|
426
530
|
res.status(200).json(healthStatus);
|
|
427
531
|
});
|
|
532
|
+
// Endpoint to provide memory usage details
|
|
428
533
|
this.expressApp.get('/memory', async (req, res) => {
|
|
429
534
|
this.log.debug('Express received /memory');
|
|
535
|
+
// Memory usage from process
|
|
430
536
|
const memoryUsageRaw = process.memoryUsage();
|
|
431
537
|
const memoryUsage = {
|
|
432
538
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -435,10 +541,13 @@ export class Frontend extends EventEmitter {
|
|
|
435
541
|
external: formatBytes(memoryUsageRaw.external),
|
|
436
542
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
437
543
|
};
|
|
544
|
+
// V8 heap statistics
|
|
438
545
|
const { default: v8 } = await import('node:v8');
|
|
439
546
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
440
547
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
548
|
+
// Format heapStats
|
|
441
549
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
550
|
+
// Format heapSpaces
|
|
442
551
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
443
552
|
...space,
|
|
444
553
|
space_size: formatBytes(space.space_size),
|
|
@@ -457,18 +566,22 @@ export class Frontend extends EventEmitter {
|
|
|
457
566
|
};
|
|
458
567
|
res.status(200).json(memoryReport);
|
|
459
568
|
});
|
|
569
|
+
// Endpoint to provide settings
|
|
460
570
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
461
571
|
this.log.debug('The frontend sent /api/settings');
|
|
462
572
|
res.json(await this.getApiSettings());
|
|
463
573
|
});
|
|
574
|
+
// Endpoint to provide plugins
|
|
464
575
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
465
576
|
this.log.debug('The frontend sent /api/plugins');
|
|
466
577
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
467
578
|
});
|
|
579
|
+
// Endpoint to provide devices
|
|
468
580
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
469
581
|
this.log.debug('The frontend sent /api/devices');
|
|
470
582
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
471
583
|
});
|
|
584
|
+
// Endpoint to view the matterbridge log
|
|
472
585
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
473
586
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
474
587
|
try {
|
|
@@ -482,6 +595,7 @@ export class Frontend extends EventEmitter {
|
|
|
482
595
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
483
596
|
}
|
|
484
597
|
});
|
|
598
|
+
// Endpoint to view the matter.js log
|
|
485
599
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
486
600
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
487
601
|
try {
|
|
@@ -495,6 +609,7 @@ export class Frontend extends EventEmitter {
|
|
|
495
609
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
496
610
|
}
|
|
497
611
|
});
|
|
612
|
+
// Endpoint to view the diagnostic.log
|
|
498
613
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
499
614
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
500
615
|
await this.generateDiagnostic();
|
|
@@ -505,10 +620,13 @@ export class Frontend extends EventEmitter {
|
|
|
505
620
|
res.send(data.slice(29));
|
|
506
621
|
}
|
|
507
622
|
catch (error) {
|
|
623
|
+
// istanbul ignore next
|
|
508
624
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
625
|
+
// istanbul ignore next
|
|
509
626
|
res.status(500).send('Error reading diagnostic log file.');
|
|
510
627
|
}
|
|
511
628
|
});
|
|
629
|
+
// Endpoint to download the diagnostic.log
|
|
512
630
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
513
631
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
514
632
|
await this.generateDiagnostic();
|
|
@@ -519,16 +637,19 @@ export class Frontend extends EventEmitter {
|
|
|
519
637
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
520
638
|
}
|
|
521
639
|
catch (error) {
|
|
640
|
+
// istanbul ignore next
|
|
522
641
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
523
642
|
}
|
|
524
643
|
res.type('text/plain');
|
|
525
644
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
645
|
+
/* istanbul ignore if */
|
|
526
646
|
if (error) {
|
|
527
647
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
528
648
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
529
649
|
}
|
|
530
650
|
});
|
|
531
651
|
});
|
|
652
|
+
// Endpoint to view the history.html
|
|
532
653
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
533
654
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
534
655
|
try {
|
|
@@ -542,6 +663,7 @@ export class Frontend extends EventEmitter {
|
|
|
542
663
|
res.status(500).send('Error reading history file.');
|
|
543
664
|
}
|
|
544
665
|
});
|
|
666
|
+
// Endpoint to download the history.html
|
|
545
667
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
546
668
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
547
669
|
try {
|
|
@@ -551,6 +673,7 @@ export class Frontend extends EventEmitter {
|
|
|
551
673
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
552
674
|
res.type('text/plain');
|
|
553
675
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
676
|
+
/* istanbul ignore if */
|
|
554
677
|
if (error) {
|
|
555
678
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
556
679
|
res.status(500).send('Error downloading history file');
|
|
@@ -562,6 +685,7 @@ export class Frontend extends EventEmitter {
|
|
|
562
685
|
res.status(500).send('Error reading history file.');
|
|
563
686
|
}
|
|
564
687
|
});
|
|
688
|
+
// Endpoint to view the shelly log
|
|
565
689
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
566
690
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
567
691
|
try {
|
|
@@ -575,6 +699,7 @@ export class Frontend extends EventEmitter {
|
|
|
575
699
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
576
700
|
}
|
|
577
701
|
});
|
|
702
|
+
// Endpoint to download the matterbridge log
|
|
578
703
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
579
704
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
580
705
|
const fs = await import('node:fs');
|
|
@@ -589,12 +714,14 @@ export class Frontend extends EventEmitter {
|
|
|
589
714
|
}
|
|
590
715
|
res.type('text/plain');
|
|
591
716
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
717
|
+
/* istanbul ignore if */
|
|
592
718
|
if (error) {
|
|
593
719
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
594
720
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
595
721
|
}
|
|
596
722
|
});
|
|
597
723
|
});
|
|
724
|
+
// Endpoint to download the matter log
|
|
598
725
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
599
726
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
600
727
|
const fs = await import('node:fs');
|
|
@@ -609,12 +736,14 @@ export class Frontend extends EventEmitter {
|
|
|
609
736
|
}
|
|
610
737
|
res.type('text/plain');
|
|
611
738
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
739
|
+
/* istanbul ignore if */
|
|
612
740
|
if (error) {
|
|
613
741
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
614
742
|
res.status(500).send('Error downloading the matter log file');
|
|
615
743
|
}
|
|
616
744
|
});
|
|
617
745
|
});
|
|
746
|
+
// Endpoint to download the shelly log
|
|
618
747
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
619
748
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
620
749
|
const fs = await import('node:fs');
|
|
@@ -629,75 +758,91 @@ export class Frontend extends EventEmitter {
|
|
|
629
758
|
}
|
|
630
759
|
res.type('text/plain');
|
|
631
760
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
761
|
+
/* istanbul ignore if */
|
|
632
762
|
if (error) {
|
|
633
763
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
634
764
|
res.status(500).send('Error downloading Shelly system log file');
|
|
635
765
|
}
|
|
636
766
|
});
|
|
637
767
|
});
|
|
768
|
+
// Endpoint to download the matterbridge storage directory
|
|
638
769
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
639
770
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
640
771
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
641
772
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
773
|
+
/* istanbul ignore if */
|
|
642
774
|
if (error) {
|
|
643
775
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
644
776
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
645
777
|
}
|
|
646
778
|
});
|
|
647
779
|
});
|
|
780
|
+
// Endpoint to download the matter storage file
|
|
648
781
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
649
782
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
650
783
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
651
784
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
785
|
+
/* istanbul ignore if */
|
|
652
786
|
if (error) {
|
|
653
787
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
654
788
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
655
789
|
}
|
|
656
790
|
});
|
|
657
791
|
});
|
|
792
|
+
// Endpoint to download the matterbridge plugin directory
|
|
658
793
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
659
794
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
660
795
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
661
796
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
797
|
+
/* istanbul ignore if */
|
|
662
798
|
if (error) {
|
|
663
799
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
664
800
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
665
801
|
}
|
|
666
802
|
});
|
|
667
803
|
});
|
|
804
|
+
// Endpoint to download the matterbridge plugin config files
|
|
668
805
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
669
806
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
670
807
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
671
808
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
809
|
+
/* istanbul ignore if */
|
|
672
810
|
if (error) {
|
|
673
811
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
674
812
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
675
813
|
}
|
|
676
814
|
});
|
|
677
815
|
});
|
|
816
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
678
817
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
679
818
|
this.log.debug('The frontend sent /api/download-backup');
|
|
680
819
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
820
|
+
/* istanbul ignore if */
|
|
681
821
|
if (error) {
|
|
682
822
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
683
823
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
684
824
|
}
|
|
685
825
|
});
|
|
686
826
|
});
|
|
827
|
+
// Endpoint to upload a package
|
|
687
828
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
688
829
|
const { filename } = req.body;
|
|
689
830
|
const file = req.file;
|
|
831
|
+
/* istanbul ignore if */
|
|
690
832
|
if (!file || !filename) {
|
|
691
833
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
692
834
|
res.status(400).send('Invalid request: file and filename are required');
|
|
693
835
|
return;
|
|
694
836
|
}
|
|
695
837
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
838
|
+
// Define the path where the plugin file will be saved
|
|
696
839
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
697
840
|
try {
|
|
841
|
+
// Move the uploaded file to the specified path
|
|
698
842
|
const fs = await import('node:fs');
|
|
699
843
|
await fs.promises.rename(file.path, filePath);
|
|
700
844
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
845
|
+
// Install the plugin package
|
|
701
846
|
if (filename.endsWith('.tgz')) {
|
|
702
847
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
703
848
|
if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
|
|
@@ -725,6 +870,7 @@ export class Frontend extends EventEmitter {
|
|
|
725
870
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
726
871
|
}
|
|
727
872
|
});
|
|
873
|
+
// Fallback for routing (must be the last route)
|
|
728
874
|
this.expressApp.use((req, res) => {
|
|
729
875
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
730
876
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -735,13 +881,16 @@ export class Frontend extends EventEmitter {
|
|
|
735
881
|
async stop() {
|
|
736
882
|
this.log.debug('Stopping the frontend...');
|
|
737
883
|
const ws = await import('ws');
|
|
884
|
+
// Remove listeners from the express app
|
|
738
885
|
if (this.expressApp) {
|
|
739
886
|
this.expressApp.removeAllListeners();
|
|
740
887
|
this.expressApp = undefined;
|
|
741
888
|
this.log.debug('Frontend app closed successfully');
|
|
742
889
|
}
|
|
890
|
+
// Close the WebSocket server
|
|
743
891
|
if (this.webSocketServer) {
|
|
744
892
|
this.log.debug('Closing WebSocket server...');
|
|
893
|
+
// Close all active connections
|
|
745
894
|
this.webSocketServer.clients.forEach((client) => {
|
|
746
895
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
747
896
|
client.close();
|
|
@@ -749,7 +898,9 @@ export class Frontend extends EventEmitter {
|
|
|
749
898
|
});
|
|
750
899
|
await withTimeout(new Promise((resolve) => {
|
|
751
900
|
this.webSocketServer?.close((error) => {
|
|
901
|
+
// istanbul ignore if
|
|
752
902
|
if (error) {
|
|
903
|
+
// istanbul ignore next
|
|
753
904
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
754
905
|
}
|
|
755
906
|
else {
|
|
@@ -762,8 +913,27 @@ export class Frontend extends EventEmitter {
|
|
|
762
913
|
this.webSocketServer.removeAllListeners();
|
|
763
914
|
this.webSocketServer = undefined;
|
|
764
915
|
}
|
|
916
|
+
// Close the http server
|
|
765
917
|
if (this.httpServer) {
|
|
766
918
|
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
|
+
*/
|
|
767
937
|
this.httpServer.close();
|
|
768
938
|
this.log.debug('Http server closed successfully');
|
|
769
939
|
this.listening = false;
|
|
@@ -772,8 +942,27 @@ export class Frontend extends EventEmitter {
|
|
|
772
942
|
this.httpServer = undefined;
|
|
773
943
|
this.log.debug('Frontend http server closed successfully');
|
|
774
944
|
}
|
|
945
|
+
// Close the https server
|
|
775
946
|
if (this.httpsServer) {
|
|
776
947
|
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
|
+
*/
|
|
777
966
|
this.httpsServer.close();
|
|
778
967
|
this.log.debug('Https server closed successfully');
|
|
779
968
|
this.listening = false;
|
|
@@ -784,7 +973,13 @@ export class Frontend extends EventEmitter {
|
|
|
784
973
|
}
|
|
785
974
|
this.log.debug('Frontend stopped successfully');
|
|
786
975
|
}
|
|
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
|
+
*/
|
|
787
981
|
async getApiSettings() {
|
|
982
|
+
// Update the variable system information properties
|
|
788
983
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
789
984
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
790
985
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -794,6 +989,7 @@ export class Frontend extends EventEmitter {
|
|
|
794
989
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
795
990
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
796
991
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
992
|
+
// Create the matterbridge information
|
|
797
993
|
const info = {
|
|
798
994
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
799
995
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -829,9 +1025,15 @@ export class Frontend extends EventEmitter {
|
|
|
829
1025
|
};
|
|
830
1026
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
831
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Retrieves the reachable attribute.
|
|
1030
|
+
*
|
|
1031
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
1032
|
+
* @returns {boolean} The reachable attribute.
|
|
1033
|
+
*/
|
|
832
1034
|
getReachability(device) {
|
|
833
1035
|
if (this.matterbridge.hasCleanupStarted)
|
|
834
|
-
return false;
|
|
1036
|
+
return false; // Skip if cleanup has started
|
|
835
1037
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
836
1038
|
return false;
|
|
837
1039
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -842,9 +1044,15 @@ export class Frontend extends EventEmitter {
|
|
|
842
1044
|
return true;
|
|
843
1045
|
return false;
|
|
844
1046
|
}
|
|
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
|
+
*/
|
|
845
1053
|
getPowerSource(endpoint) {
|
|
846
1054
|
if (this.matterbridge.hasCleanupStarted)
|
|
847
|
-
return undefined;
|
|
1055
|
+
return undefined; // Skip if cleanup has started
|
|
848
1056
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
849
1057
|
return undefined;
|
|
850
1058
|
const powerSource = (device) => {
|
|
@@ -859,16 +1067,25 @@ export class Frontend extends EventEmitter {
|
|
|
859
1067
|
}
|
|
860
1068
|
return;
|
|
861
1069
|
};
|
|
1070
|
+
// Root endpoint
|
|
862
1071
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
863
1072
|
return powerSource(endpoint);
|
|
1073
|
+
// Child endpoints
|
|
864
1074
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1075
|
+
// istanbul ignore else
|
|
865
1076
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
866
1077
|
return powerSource(child);
|
|
867
1078
|
}
|
|
868
1079
|
}
|
|
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
|
+
*/
|
|
869
1086
|
getBatteryLevel(endpoint) {
|
|
870
1087
|
if (this.matterbridge.hasCleanupStarted)
|
|
871
|
-
return undefined;
|
|
1088
|
+
return undefined; // Skip if cleanup has started
|
|
872
1089
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
873
1090
|
return undefined;
|
|
874
1091
|
const batteryLevel = (device) => {
|
|
@@ -879,16 +1096,27 @@ export class Frontend extends EventEmitter {
|
|
|
879
1096
|
}
|
|
880
1097
|
return undefined;
|
|
881
1098
|
};
|
|
1099
|
+
// Root endpoint
|
|
882
1100
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
883
1101
|
return batteryLevel(endpoint);
|
|
1102
|
+
// Child endpoints
|
|
884
1103
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1104
|
+
// istanbul ignore else
|
|
885
1105
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
886
1106
|
return batteryLevel(child);
|
|
887
1107
|
}
|
|
888
1108
|
}
|
|
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
|
+
*/
|
|
889
1116
|
getClusterTextFromDevice(device) {
|
|
890
1117
|
if (this.matterbridge.hasCleanupStarted)
|
|
891
|
-
return '';
|
|
1118
|
+
return ''; // Skip if cleanup has started
|
|
1119
|
+
// istanbul ignore else
|
|
892
1120
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
893
1121
|
return '';
|
|
894
1122
|
const getUserLabel = (device) => {
|
|
@@ -898,6 +1126,7 @@ export class Frontend extends EventEmitter {
|
|
|
898
1126
|
if (composed)
|
|
899
1127
|
return 'Composed: ' + composed.value;
|
|
900
1128
|
}
|
|
1129
|
+
// istanbul ignore next cause is not reachable
|
|
901
1130
|
return '';
|
|
902
1131
|
};
|
|
903
1132
|
const getFixedLabel = (device) => {
|
|
@@ -907,11 +1136,13 @@ export class Frontend extends EventEmitter {
|
|
|
907
1136
|
if (composed)
|
|
908
1137
|
return 'Composed: ' + composed.value;
|
|
909
1138
|
}
|
|
1139
|
+
// istanbul ignore next cause is not reacheable
|
|
910
1140
|
return '';
|
|
911
1141
|
};
|
|
912
1142
|
let attributes = '';
|
|
913
1143
|
let supportedModes = [];
|
|
914
1144
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1145
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
915
1146
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
916
1147
|
return;
|
|
917
1148
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1001,11 +1232,17 @@ export class Frontend extends EventEmitter {
|
|
|
1001
1232
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1002
1233
|
attributes += `${getUserLabel(device)} `;
|
|
1003
1234
|
});
|
|
1235
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1004
1236
|
return attributes.trimStart().trimEnd();
|
|
1005
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1240
|
+
*
|
|
1241
|
+
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1242
|
+
*/
|
|
1006
1243
|
getPlugins() {
|
|
1007
1244
|
if (this.matterbridge.hasCleanupStarted)
|
|
1008
|
-
return [];
|
|
1245
|
+
return []; // Skip if cleanup has started
|
|
1009
1246
|
const plugins = [];
|
|
1010
1247
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1011
1248
|
plugins.push({
|
|
@@ -1033,18 +1270,27 @@ export class Frontend extends EventEmitter {
|
|
|
1033
1270
|
schemaJson: plugin.schemaJson,
|
|
1034
1271
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1035
1272
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1273
|
+
// Childbridge mode specific data
|
|
1036
1274
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1037
1275
|
});
|
|
1038
1276
|
}
|
|
1039
1277
|
return plugins;
|
|
1040
1278
|
}
|
|
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
|
+
*/
|
|
1041
1285
|
getDevices(pluginName) {
|
|
1042
1286
|
if (this.matterbridge.hasCleanupStarted)
|
|
1043
|
-
return [];
|
|
1287
|
+
return []; // Skip if cleanup has started
|
|
1044
1288
|
const devices = [];
|
|
1045
1289
|
for (const device of this.matterbridge.devices.array()) {
|
|
1290
|
+
// Filter by pluginName if provided
|
|
1046
1291
|
if (pluginName && pluginName !== device.plugin)
|
|
1047
1292
|
continue;
|
|
1293
|
+
// Check if the device has the required properties
|
|
1048
1294
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1049
1295
|
continue;
|
|
1050
1296
|
devices.push({
|
|
@@ -1065,24 +1311,39 @@ export class Frontend extends EventEmitter {
|
|
|
1065
1311
|
}
|
|
1066
1312
|
return devices;
|
|
1067
1313
|
}
|
|
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
|
+
*/
|
|
1068
1323
|
getClusters(pluginName, endpointNumber) {
|
|
1069
1324
|
if (this.matterbridge.hasCleanupStarted)
|
|
1070
|
-
return;
|
|
1325
|
+
return; // Skip if cleanup has started
|
|
1071
1326
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1072
1327
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1073
1328
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1074
1329
|
return;
|
|
1075
1330
|
}
|
|
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
|
|
1076
1333
|
const deviceTypes = [];
|
|
1077
1334
|
const clusters = [];
|
|
1078
1335
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1079
1336
|
deviceTypes.push(d.deviceType);
|
|
1080
1337
|
});
|
|
1338
|
+
// Get the clusters from the main endpoint
|
|
1081
1339
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1082
1340
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1083
1341
|
return;
|
|
1084
1342
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1085
1343
|
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
|
+
// );
|
|
1086
1347
|
clusters.push({
|
|
1087
1348
|
endpoint: endpoint.number.toString(),
|
|
1088
1349
|
number: endpoint.number,
|
|
@@ -1096,12 +1357,19 @@ export class Frontend extends EventEmitter {
|
|
|
1096
1357
|
attributeLocalValue: attributeValue,
|
|
1097
1358
|
});
|
|
1098
1359
|
});
|
|
1360
|
+
// Get the child endpoints
|
|
1099
1361
|
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
|
+
// }
|
|
1100
1365
|
childEndpoints.forEach((childEndpoint) => {
|
|
1366
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1101
1367
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1102
1368
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1103
1369
|
return;
|
|
1104
1370
|
}
|
|
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
|
|
1105
1373
|
const deviceTypes = [];
|
|
1106
1374
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1107
1375
|
deviceTypes.push(d.deviceType);
|
|
@@ -1111,6 +1379,9 @@ export class Frontend extends EventEmitter {
|
|
|
1111
1379
|
return;
|
|
1112
1380
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1113
1381
|
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
|
+
// );
|
|
1114
1385
|
clusters.push({
|
|
1115
1386
|
endpoint: childEndpoint.number.toString(),
|
|
1116
1387
|
number: childEndpoint.number,
|
|
@@ -1130,6 +1401,7 @@ export class Frontend extends EventEmitter {
|
|
|
1130
1401
|
async generateDiagnostic() {
|
|
1131
1402
|
this.log.debug('Generating diagnostic...');
|
|
1132
1403
|
const serverNodes = [];
|
|
1404
|
+
// istanbul ignore else
|
|
1133
1405
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1134
1406
|
if (this.matterbridge.serverNode)
|
|
1135
1407
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1140,6 +1412,7 @@ export class Frontend extends EventEmitter {
|
|
|
1140
1412
|
serverNodes.push(plugin.serverNode);
|
|
1141
1413
|
}
|
|
1142
1414
|
}
|
|
1415
|
+
// istanbul ignore next
|
|
1143
1416
|
for (const device of this.matterbridge.devices.array()) {
|
|
1144
1417
|
if (device.serverNode)
|
|
1145
1418
|
serverNodes.push(device.serverNode);
|
|
@@ -1163,8 +1436,15 @@ export class Frontend extends EventEmitter {
|
|
|
1163
1436
|
values: [...serverNodes],
|
|
1164
1437
|
})));
|
|
1165
1438
|
delete Logger.destinations.diagnostic;
|
|
1166
|
-
await wait(500);
|
|
1439
|
+
await wait(500); // Wait for the log to be written
|
|
1167
1440
|
}
|
|
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
|
+
*/
|
|
1168
1448
|
async wsMessageHandler(client, message) {
|
|
1169
1449
|
let data;
|
|
1170
1450
|
const sendResponse = (data) => {
|
|
@@ -1182,12 +1462,13 @@ export class Frontend extends EventEmitter {
|
|
|
1182
1462
|
client.send(JSON.stringify(data));
|
|
1183
1463
|
}
|
|
1184
1464
|
else {
|
|
1465
|
+
// istanbul ignore next cause is only a safety check
|
|
1185
1466
|
this.log.error('Cannot send api response, client not connected');
|
|
1186
1467
|
}
|
|
1187
1468
|
};
|
|
1188
1469
|
try {
|
|
1189
1470
|
data = JSON.parse(message.toString());
|
|
1190
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1471
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
1191
1472
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1192
1473
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1193
1474
|
return;
|
|
@@ -1244,7 +1525,22 @@ export class Frontend extends EventEmitter {
|
|
|
1244
1525
|
}
|
|
1245
1526
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1246
1527
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1247
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1528
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
|
|
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
|
+
*/
|
|
1248
1544
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1249
1545
|
if (plugin) {
|
|
1250
1546
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1257,7 +1553,7 @@ export class Frontend extends EventEmitter {
|
|
|
1257
1553
|
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1258
1554
|
return;
|
|
1259
1555
|
})
|
|
1260
|
-
.catch((_error) => { });
|
|
1556
|
+
.catch(/* istanbul ignore next */ (_error) => { });
|
|
1261
1557
|
}
|
|
1262
1558
|
else {
|
|
1263
1559
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
@@ -1271,6 +1567,10 @@ export class Frontend extends EventEmitter {
|
|
|
1271
1567
|
}
|
|
1272
1568
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1273
1569
|
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
|
+
*/
|
|
1274
1574
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1275
1575
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1276
1576
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1286,28 +1586,29 @@ export class Frontend extends EventEmitter {
|
|
|
1286
1586
|
return;
|
|
1287
1587
|
}
|
|
1288
1588
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
this.matterbridge.plugins
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
this.
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
})
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1589
|
+
plugin.locked = undefined;
|
|
1590
|
+
plugin.error = undefined;
|
|
1591
|
+
plugin.loaded = undefined;
|
|
1592
|
+
plugin.started = undefined;
|
|
1593
|
+
plugin.configured = undefined;
|
|
1594
|
+
plugin.platform = undefined;
|
|
1595
|
+
plugin.registeredDevices = undefined;
|
|
1596
|
+
plugin.matter = undefined;
|
|
1597
|
+
await this.matterbridge.plugins.enable(data.params.pluginName);
|
|
1598
|
+
this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
|
|
1599
|
+
setImmediate(async () => {
|
|
1600
|
+
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
1601
|
+
// @ts-expect-error Accessing private method
|
|
1602
|
+
if (plugin.serverNode)
|
|
1603
|
+
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1604
|
+
// @ts-expect-error Accessing private method
|
|
1605
|
+
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1606
|
+
await this.matterbridge.startServerNode(device.serverNode);
|
|
1607
|
+
this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
|
|
1608
|
+
this.wssSendRefreshRequired('plugins');
|
|
1609
|
+
this.wssSendRefreshRequired('devices');
|
|
1610
|
+
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, success: true });
|
|
1611
|
+
});
|
|
1311
1612
|
}
|
|
1312
1613
|
else if (data.method === '/api/disableplugin') {
|
|
1313
1614
|
if (!isValidString(data.params.pluginName, 10) || !this.matterbridge.plugins.has(data.params.pluginName)) {
|
|
@@ -1315,6 +1616,15 @@ export class Frontend extends EventEmitter {
|
|
|
1315
1616
|
return;
|
|
1316
1617
|
}
|
|
1317
1618
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1619
|
+
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
|
|
1620
|
+
// @ts-expect-error Accessing private method
|
|
1621
|
+
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1622
|
+
device.serverNode = undefined;
|
|
1623
|
+
}
|
|
1624
|
+
// @ts-expect-error Accessing private method
|
|
1625
|
+
if (plugin.serverNode)
|
|
1626
|
+
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1627
|
+
plugin.serverNode = undefined;
|
|
1318
1628
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
1319
1629
|
await this.matterbridge.plugins.disable(data.params.pluginName);
|
|
1320
1630
|
this.wssSendSnackbarMessage(`Disabled plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1327,32 +1637,42 @@ export class Frontend extends EventEmitter {
|
|
|
1327
1637
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/restartplugin' });
|
|
1328
1638
|
return;
|
|
1329
1639
|
}
|
|
1640
|
+
this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
|
|
1330
1641
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1331
1642
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1643
|
+
// Stop server nodes
|
|
1332
1644
|
if (plugin.serverNode) {
|
|
1645
|
+
// @ts-expect-error Accessing private method
|
|
1333
1646
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1334
1647
|
plugin.serverNode = undefined;
|
|
1335
1648
|
}
|
|
1336
|
-
for (const device of this.matterbridge.devices) {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
this.matterbridge.
|
|
1340
|
-
|
|
1649
|
+
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
|
|
1650
|
+
// @ts-expect-error Accessing private method
|
|
1651
|
+
if (device.serverNode)
|
|
1652
|
+
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1653
|
+
device.serverNode = undefined;
|
|
1654
|
+
this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
|
|
1655
|
+
this.matterbridge.devices.remove(device);
|
|
1341
1656
|
}
|
|
1657
|
+
// @ts-expect-error Accessing private method
|
|
1342
1658
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1343
1659
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1344
1660
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1345
|
-
plugin.restartRequired = false;
|
|
1661
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1346
1662
|
let needRestart = 0;
|
|
1347
1663
|
for (const plugin of this.matterbridge.plugins) {
|
|
1348
1664
|
if (plugin.restartRequired)
|
|
1349
1665
|
needRestart++;
|
|
1350
1666
|
}
|
|
1351
|
-
if (needRestart === 0)
|
|
1352
|
-
this.wssSendRestartNotRequired(true);
|
|
1353
|
-
|
|
1667
|
+
if (needRestart === 0)
|
|
1668
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1669
|
+
// Start server nodes
|
|
1670
|
+
// @ts-expect-error Accessing private method
|
|
1354
1671
|
if (plugin.serverNode)
|
|
1355
1672
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1673
|
+
// @ts-expect-error Accessing private method
|
|
1674
|
+
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1675
|
+
await this.matterbridge.startServerNode(device.serverNode);
|
|
1356
1676
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
1357
1677
|
this.wssSendRefreshRequired('plugins');
|
|
1358
1678
|
this.wssSendRefreshRequired('devices');
|
|
@@ -1495,6 +1815,9 @@ export class Frontend extends EventEmitter {
|
|
|
1495
1815
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1496
1816
|
}
|
|
1497
1817
|
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
|
|
1498
1821
|
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1499
1822
|
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1500
1823
|
await advertiser.advertise(true);
|
|
@@ -1558,6 +1881,7 @@ export class Frontend extends EventEmitter {
|
|
|
1558
1881
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
|
|
1559
1882
|
return;
|
|
1560
1883
|
}
|
|
1884
|
+
// istanbul ignore next
|
|
1561
1885
|
const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1562
1886
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
|
|
1563
1887
|
}
|
|
@@ -1571,6 +1895,7 @@ export class Frontend extends EventEmitter {
|
|
|
1571
1895
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
|
|
1572
1896
|
return;
|
|
1573
1897
|
}
|
|
1898
|
+
// istanbul ignore next
|
|
1574
1899
|
const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1575
1900
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
|
|
1576
1901
|
}
|
|
@@ -1622,22 +1947,22 @@ export class Frontend extends EventEmitter {
|
|
|
1622
1947
|
if (isValidString(data.params.value, 4)) {
|
|
1623
1948
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1624
1949
|
if (data.params.value === 'Debug') {
|
|
1625
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1950
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1626
1951
|
}
|
|
1627
1952
|
else if (data.params.value === 'Info') {
|
|
1628
|
-
await this.matterbridge.setLogLevel("info");
|
|
1953
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1629
1954
|
}
|
|
1630
1955
|
else if (data.params.value === 'Notice') {
|
|
1631
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1956
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1632
1957
|
}
|
|
1633
1958
|
else if (data.params.value === 'Warn') {
|
|
1634
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1959
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1635
1960
|
}
|
|
1636
1961
|
else if (data.params.value === 'Error') {
|
|
1637
|
-
await this.matterbridge.setLogLevel("error");
|
|
1962
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1638
1963
|
}
|
|
1639
1964
|
else if (data.params.value === 'Fatal') {
|
|
1640
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1965
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1641
1966
|
}
|
|
1642
1967
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1643
1968
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1648,6 +1973,7 @@ export class Frontend extends EventEmitter {
|
|
|
1648
1973
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1649
1974
|
this.matterbridge.fileLogger = data.params.value;
|
|
1650
1975
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1976
|
+
// Create the file logger for matterbridge
|
|
1651
1977
|
if (data.params.value)
|
|
1652
1978
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1653
1979
|
else
|
|
@@ -1677,11 +2003,12 @@ export class Frontend extends EventEmitter {
|
|
|
1677
2003
|
Logger.level = MatterLogLevel.FATAL;
|
|
1678
2004
|
}
|
|
1679
2005
|
this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
2006
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
2007
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
2008
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
2009
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
2010
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
2011
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1685
2012
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1686
2013
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1687
2014
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1733,6 +2060,7 @@ export class Frontend extends EventEmitter {
|
|
|
1733
2060
|
}
|
|
1734
2061
|
break;
|
|
1735
2062
|
case 'setmatterport':
|
|
2063
|
+
// eslint-disable-next-line no-case-declarations
|
|
1736
2064
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1737
2065
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1738
2066
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1752,6 +2080,7 @@ export class Frontend extends EventEmitter {
|
|
|
1752
2080
|
}
|
|
1753
2081
|
break;
|
|
1754
2082
|
case 'setmatterdiscriminator':
|
|
2083
|
+
// eslint-disable-next-line no-case-declarations
|
|
1755
2084
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1756
2085
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1757
2086
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1771,6 +2100,7 @@ export class Frontend extends EventEmitter {
|
|
|
1771
2100
|
}
|
|
1772
2101
|
break;
|
|
1773
2102
|
case 'setmatterpasscode':
|
|
2103
|
+
// eslint-disable-next-line no-case-declarations
|
|
1774
2104
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1775
2105
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1776
2106
|
this.matterbridge.passcode = passcode;
|
|
@@ -1816,15 +2146,19 @@ export class Frontend extends EventEmitter {
|
|
|
1816
2146
|
return;
|
|
1817
2147
|
}
|
|
1818
2148
|
const config = plugin.configJson;
|
|
2149
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1819
2150
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2151
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1820
2152
|
if (select === 'serial')
|
|
1821
2153
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1822
2154
|
if (select === 'name')
|
|
1823
2155
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1824
2156
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2157
|
+
// Remove postfix from the serial if it exists
|
|
1825
2158
|
if (config.postfix) {
|
|
1826
2159
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1827
2160
|
}
|
|
2161
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1828
2162
|
if (isValidArray(config.whiteList, 1)) {
|
|
1829
2163
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1830
2164
|
config.whiteList.push(data.params.serial);
|
|
@@ -1833,6 +2167,7 @@ export class Frontend extends EventEmitter {
|
|
|
1833
2167
|
config.whiteList.push(data.params.name);
|
|
1834
2168
|
}
|
|
1835
2169
|
}
|
|
2170
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1836
2171
|
if (isValidArray(config.blackList, 1)) {
|
|
1837
2172
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1838
2173
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1860,7 +2195,9 @@ export class Frontend extends EventEmitter {
|
|
|
1860
2195
|
return;
|
|
1861
2196
|
}
|
|
1862
2197
|
const config = plugin.configJson;
|
|
2198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1863
2199
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2200
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1864
2201
|
if (select === 'serial')
|
|
1865
2202
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1866
2203
|
if (select === 'name')
|
|
@@ -1869,6 +2206,7 @@ export class Frontend extends EventEmitter {
|
|
|
1869
2206
|
if (config.postfix) {
|
|
1870
2207
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1871
2208
|
}
|
|
2209
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1872
2210
|
if (isValidArray(config.whiteList, 1)) {
|
|
1873
2211
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1874
2212
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1877,6 +2215,7 @@ export class Frontend extends EventEmitter {
|
|
|
1877
2215
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1878
2216
|
}
|
|
1879
2217
|
}
|
|
2218
|
+
// Add the serial to the blackList
|
|
1880
2219
|
if (isValidArray(config.blackList)) {
|
|
1881
2220
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1882
2221
|
config.blackList.push(data.params.serial);
|
|
@@ -1899,6 +2238,7 @@ export class Frontend extends EventEmitter {
|
|
|
1899
2238
|
}
|
|
1900
2239
|
}
|
|
1901
2240
|
else {
|
|
2241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1902
2242
|
const localData = data;
|
|
1903
2243
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1904
2244
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1908,23 +2248,46 @@ export class Frontend extends EventEmitter {
|
|
|
1908
2248
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1909
2249
|
}
|
|
1910
2250
|
}
|
|
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
|
+
*/
|
|
1911
2264
|
wssSendLogMessage(level, time, name, message) {
|
|
1912
2265
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1913
2266
|
return;
|
|
1914
2267
|
if (!level || !time || !name || !message)
|
|
1915
2268
|
return;
|
|
2269
|
+
// Remove ANSI escape codes from the message
|
|
2270
|
+
// eslint-disable-next-line no-control-regex
|
|
1916
2271
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2272
|
+
// Remove leading asterisks from the message
|
|
1917
2273
|
message = message.replace(/^\*+/, '');
|
|
2274
|
+
// Replace all occurrences of \t and \n
|
|
1918
2275
|
message = message.replace(/[\t\n]/g, '');
|
|
2276
|
+
// Remove non-printable characters
|
|
2277
|
+
// eslint-disable-next-line no-control-regex
|
|
1919
2278
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2279
|
+
// Replace all occurrences of \" with "
|
|
1920
2280
|
message = message.replace(/\\"/g, '"');
|
|
2281
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1921
2282
|
const maxContinuousLength = 100;
|
|
1922
2283
|
const keepStartLength = 20;
|
|
1923
2284
|
const keepEndLength = 20;
|
|
2285
|
+
// Split the message into words
|
|
1924
2286
|
if (level !== 'spawn') {
|
|
1925
2287
|
message = message
|
|
1926
2288
|
.split(' ')
|
|
1927
2289
|
.map((word) => {
|
|
2290
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1928
2291
|
if (word.length > maxContinuousLength) {
|
|
1929
2292
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1930
2293
|
}
|
|
@@ -1932,14 +2295,34 @@ export class Frontend extends EventEmitter {
|
|
|
1932
2295
|
})
|
|
1933
2296
|
.join(' ');
|
|
1934
2297
|
}
|
|
2298
|
+
// Send the message to all connected clients
|
|
1935
2299
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1936
2300
|
}
|
|
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
|
+
*/
|
|
1937
2313
|
wssSendRefreshRequired(changed, params) {
|
|
1938
2314
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1939
2315
|
return;
|
|
1940
2316
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2317
|
+
// Send the message to all connected clients
|
|
1941
2318
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1942
2319
|
}
|
|
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
|
+
*/
|
|
1943
2326
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1944
2327
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1945
2328
|
return;
|
|
@@ -1948,8 +2331,14 @@ export class Frontend extends EventEmitter {
|
|
|
1948
2331
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
1949
2332
|
if (snackbar === true)
|
|
1950
2333
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2334
|
+
// Send the message to all connected clients
|
|
1951
2335
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1952
2336
|
}
|
|
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
|
+
*/
|
|
1953
2342
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1954
2343
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1955
2344
|
return;
|
|
@@ -1957,64 +2346,145 @@ export class Frontend extends EventEmitter {
|
|
|
1957
2346
|
this.matterbridge.restartRequired = false;
|
|
1958
2347
|
if (snackbar === true)
|
|
1959
2348
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2349
|
+
// Send the message to all connected clients
|
|
1960
2350
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1961
2351
|
}
|
|
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
|
+
*/
|
|
1962
2357
|
wssSendUpdateRequired(devVersion = false) {
|
|
1963
2358
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1964
2359
|
return;
|
|
1965
2360
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1966
2361
|
this.matterbridge.updateRequired = true;
|
|
2362
|
+
// Send the message to all connected clients
|
|
1967
2363
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1968
2364
|
}
|
|
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
|
+
*/
|
|
1969
2371
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
1970
2372
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1971
2373
|
return;
|
|
2374
|
+
// istanbul ignore else
|
|
1972
2375
|
if (hasParameter('debug'))
|
|
1973
2376
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2377
|
+
// Send the message to all connected clients
|
|
1974
2378
|
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 } });
|
|
1975
2379
|
}
|
|
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
|
+
*/
|
|
1976
2391
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1977
2392
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1978
2393
|
return;
|
|
2394
|
+
// istanbul ignore else
|
|
1979
2395
|
if (hasParameter('debug'))
|
|
1980
2396
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2397
|
+
// Send the message to all connected clients
|
|
1981
2398
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1982
2399
|
}
|
|
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
|
+
*/
|
|
1983
2406
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1984
2407
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1985
2408
|
return;
|
|
2409
|
+
// istanbul ignore else
|
|
1986
2410
|
if (hasParameter('debug'))
|
|
1987
2411
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2412
|
+
// Send the message to all connected clients
|
|
1988
2413
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1989
2414
|
}
|
|
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
|
+
*/
|
|
1990
2426
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1991
2427
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1992
2428
|
return;
|
|
1993
2429
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2430
|
+
// Send the message to all connected clients
|
|
1994
2431
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1995
2432
|
}
|
|
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
|
+
*/
|
|
1996
2439
|
wssSendCloseSnackbarMessage(message) {
|
|
1997
2440
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1998
2441
|
return;
|
|
1999
2442
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2443
|
+
// Send the message to all connected clients
|
|
2000
2444
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2001
2445
|
}
|
|
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
|
+
*/
|
|
2002
2462
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2003
2463
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2004
2464
|
return;
|
|
2005
2465
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2466
|
+
// Send the message to all connected clients
|
|
2006
2467
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2007
2468
|
}
|
|
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
|
+
*/
|
|
2008
2475
|
wssBroadcastMessage(msg) {
|
|
2009
2476
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2010
2477
|
return;
|
|
2478
|
+
// Send the message to all connected clients
|
|
2011
2479
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2012
2480
|
if (msg.method !== 'log')
|
|
2013
2481
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
2014
2482
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2483
|
+
// istanbul ignore else
|
|
2015
2484
|
if (client.readyState === client.OPEN) {
|
|
2016
2485
|
client.send(stringifiedMsg);
|
|
2017
2486
|
}
|
|
2018
2487
|
});
|
|
2019
2488
|
}
|
|
2020
2489
|
}
|
|
2490
|
+
//# sourceMappingURL=frontend.js.map
|