@matterbridge/core 3.5.3 → 3.5.4-dev-20260211-520e349
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/README.md +1 -1
- package/dist/cli.d.ts +0 -24
- package/dist/cli.js +1 -97
- package/dist/cliEmitter.d.ts +0 -36
- package/dist/cliEmitter.js +0 -37
- package/dist/cliHistory.d.ts +0 -42
- package/dist/cliHistory.js +0 -38
- package/dist/clusters/export.d.ts +0 -1
- package/dist/clusters/export.js +0 -2
- package/dist/deviceManager.d.ts +0 -108
- package/dist/deviceManager.js +1 -114
- package/dist/devices/airConditioner.d.ts +0 -75
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/basicVideoPlayer.d.ts +0 -58
- package/dist/devices/basicVideoPlayer.js +1 -56
- package/dist/devices/batteryStorage.d.ts +0 -43
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/castingVideoPlayer.d.ts +0 -63
- package/dist/devices/castingVideoPlayer.js +2 -65
- package/dist/devices/cooktop.d.ts +0 -55
- package/dist/devices/cooktop.js +0 -56
- package/dist/devices/dishwasher.d.ts +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.d.ts +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.d.ts +0 -1
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.d.ts +0 -41
- package/dist/devices/extractorHood.js +0 -43
- package/dist/devices/heatPump.d.ts +0 -43
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.d.ts +0 -58
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.d.ts +0 -64
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.d.ts +1 -77
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.d.ts +0 -82
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.d.ts +0 -100
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -83
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.d.ts +0 -36
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.d.ts +0 -79
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.d.ts +0 -21
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.d.ts +0 -74
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/export.d.ts +0 -1
- package/dist/dgram/export.js +0 -1
- package/dist/export.d.ts +0 -23
- package/dist/export.js +0 -28
- package/dist/frontend.d.ts +0 -187
- package/dist/frontend.js +62 -581
- package/dist/helpers.d.ts +0 -43
- package/dist/helpers.js +0 -86
- package/dist/jestutils/export.d.ts +0 -1
- package/dist/jestutils/export.js +0 -1
- package/dist/jestutils/jestHelpers.d.ts +0 -259
- package/dist/jestutils/jestHelpers.js +15 -396
- package/dist/matter/behaviors.d.ts +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.d.ts +0 -1
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.d.ts +0 -1
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.d.ts +0 -1
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.d.ts +0 -1
- package/dist/matter/export.js +0 -2
- package/dist/matter/types.d.ts +0 -1
- package/dist/matter/types.js +0 -2
- package/dist/matterNode.d.ts +0 -258
- package/dist/matterNode.js +8 -356
- package/dist/matterbridge.d.ts +0 -389
- package/dist/matterbridge.js +48 -878
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.js +0 -50
- package/dist/matterbridgeBehaviors.d.ts +0 -24
- package/dist/matterbridgeBehaviors.js +5 -65
- package/dist/matterbridgeDeviceTypes.d.ts +0 -649
- package/dist/matterbridgeDeviceTypes.js +6 -673
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.js +0 -50
- package/dist/matterbridgeEndpoint.d.ts +0 -1369
- package/dist/matterbridgeEndpoint.js +56 -1514
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -425
- package/dist/matterbridgeEndpointHelpers.js +20 -483
- package/dist/matterbridgeEndpointTypes.d.ts +0 -70
- package/dist/matterbridgeEndpointTypes.js +0 -25
- package/dist/matterbridgePlatform.d.ts +0 -434
- package/dist/matterbridgePlatform.js +1 -473
- package/dist/pluginManager.d.ts +0 -307
- package/dist/pluginManager.js +6 -346
- package/dist/spawn.d.ts +0 -32
- package/dist/spawn.js +1 -71
- package/dist/utils/export.d.ts +0 -1
- package/dist/utils/export.js +0 -1
- package/package.json +27 -6
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/cliHistory.d.ts.map +0 -1
- package/dist/cliHistory.js.map +0 -1
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/crypto/attestationDecoder.d.ts +0 -180
- package/dist/crypto/attestationDecoder.d.ts.map +0 -1
- package/dist/crypto/attestationDecoder.js +0 -176
- package/dist/crypto/attestationDecoder.js.map +0 -1
- package/dist/crypto/declarationDecoder.d.ts +0 -72
- package/dist/crypto/declarationDecoder.d.ts.map +0 -1
- package/dist/crypto/declarationDecoder.js +0 -241
- package/dist/crypto/declarationDecoder.js.map +0 -1
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.d.ts +0 -9
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.d.ts.map +0 -1
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.js +0 -120
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.js.map +0 -1
- package/dist/crypto/read-extensions.d.ts +0 -2
- package/dist/crypto/read-extensions.d.ts.map +0 -1
- package/dist/crypto/read-extensions.js +0 -81
- package/dist/crypto/read-extensions.js.map +0 -1
- package/dist/crypto/testData.d.ts +0 -31
- package/dist/crypto/testData.d.ts.map +0 -1
- package/dist/crypto/testData.js +0 -131
- package/dist/crypto/testData.js.map +0 -1
- package/dist/crypto/walk-der.d.ts +0 -2
- package/dist/crypto/walk-der.d.ts.map +0 -1
- package/dist/crypto/walk-der.js +0 -165
- package/dist/crypto/walk-der.js.map +0 -1
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/basicVideoPlayer.d.ts.map +0 -1
- package/dist/devices/basicVideoPlayer.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/castingVideoPlayer.d.ts.map +0 -1
- package/dist/devices/castingVideoPlayer.js.map +0 -1
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/export.d.ts.map +0 -1
- package/dist/dgram/export.js.map +0 -1
- package/dist/export.d.ts.map +0 -1
- package/dist/export.js.map +0 -1
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/jestutils/export.d.ts.map +0 -1
- package/dist/jestutils/export.js.map +0 -1
- package/dist/jestutils/jestHelpers.d.ts.map +0 -1
- package/dist/jestutils/jestHelpers.js.map +0 -1
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterNode.d.ts.map +0 -1
- package/dist/matterNode.js.map +0 -1
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
- package/dist/matterbridgeEndpointTypes.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/mb_coap.d.ts +0 -24
- package/dist/mb_coap.d.ts.map +0 -1
- package/dist/mb_coap.js +0 -89
- package/dist/mb_coap.js.map +0 -1
- package/dist/mb_health.d.ts +0 -77
- package/dist/mb_health.d.ts.map +0 -1
- package/dist/mb_health.js +0 -147
- package/dist/mb_health.js.map +0 -1
- package/dist/mb_mdns.d.ts +0 -24
- package/dist/mb_mdns.d.ts.map +0 -1
- package/dist/mb_mdns.js +0 -285
- package/dist/mb_mdns.js.map +0 -1
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/spawn.d.ts.map +0 -1
- package/dist/spawn.js.map +0 -1
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/workers/brand.d.ts +0 -25
- package/dist/workers/brand.d.ts.map +0 -1
- package/dist/workers/brand.extend.d.ts +0 -10
- package/dist/workers/brand.extend.d.ts.map +0 -1
- package/dist/workers/brand.extend.js +0 -15
- package/dist/workers/brand.extend.js.map +0 -1
- package/dist/workers/brand.invalid.d.ts +0 -9
- package/dist/workers/brand.invalid.d.ts.map +0 -1
- package/dist/workers/brand.invalid.js +0 -19
- package/dist/workers/brand.invalid.js.map +0 -1
- package/dist/workers/brand.js +0 -67
- package/dist/workers/brand.js.map +0 -1
- package/dist/workers/clusterTypes.d.ts +0 -47
- package/dist/workers/clusterTypes.d.ts.map +0 -1
- package/dist/workers/clusterTypes.js +0 -57
- package/dist/workers/clusterTypes.js.map +0 -1
- package/dist/workers/frontendWorker.d.ts +0 -2
- package/dist/workers/frontendWorker.d.ts.map +0 -1
- package/dist/workers/frontendWorker.js +0 -90
- package/dist/workers/frontendWorker.js.map +0 -1
- package/dist/workers/helloWorld.d.ts +0 -2
- package/dist/workers/helloWorld.d.ts.map +0 -1
- package/dist/workers/helloWorld.js +0 -135
- package/dist/workers/helloWorld.js.map +0 -1
- package/dist/workers/matterWorker.d.ts +0 -2
- package/dist/workers/matterWorker.d.ts.map +0 -1
- package/dist/workers/matterWorker.js +0 -104
- package/dist/workers/matterWorker.js.map +0 -1
- package/dist/workers/matterbridgeWorker.d.ts +0 -2
- package/dist/workers/matterbridgeWorker.d.ts.map +0 -1
- package/dist/workers/matterbridgeWorker.js +0 -75
- package/dist/workers/matterbridgeWorker.js.map +0 -1
- package/dist/workers/messageLab.d.ts +0 -134
- package/dist/workers/messageLab.d.ts.map +0 -1
- package/dist/workers/messageLab.js +0 -129
- package/dist/workers/messageLab.js.map +0 -1
- package/dist/workers/testWorker.d.ts +0 -2
- package/dist/workers/testWorker.d.ts.map +0 -1
- package/dist/workers/testWorker.js +0 -45
- package/dist/workers/testWorker.js.map +0 -1
- package/dist/workers/usage.d.ts +0 -19
- package/dist/workers/usage.d.ts.map +0 -1
- package/dist/workers/usage.js +0 -140
- package/dist/workers/usage.js.map +0 -1
- package/dist/workers/workerManager.d.ts +0 -115
- package/dist/workers/workerManager.d.ts.map +0 -1
- package/dist/workers/workerManager.js +0 -464
- package/dist/workers/workerManager.js.map +0 -1
- package/dist/workers/workerServer.d.ts +0 -126
- package/dist/workers/workerServer.d.ts.map +0 -1
- package/dist/workers/workerServer.js +0 -340
- package/dist/workers/workerServer.js.map +0 -1
- package/dist/workers/workerTypes.d.ts +0 -23
- package/dist/workers/workerTypes.d.ts.map +0 -1
- package/dist/workers/workerTypes.js +0 -3
- package/dist/workers/workerTypes.js.map +0 -1
package/dist/frontend.js
CHANGED
|
@@ -1,34 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file contains the class Frontend.
|
|
3
|
-
*
|
|
4
|
-
* @file frontend.ts
|
|
5
|
-
* @author Luca Liguori
|
|
6
|
-
* @created 2025-01-13
|
|
7
|
-
* @version 1.3.3
|
|
8
|
-
* @license Apache-2.0
|
|
9
|
-
*
|
|
10
|
-
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
-
*
|
|
12
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
-
* you may not use this file except in compliance with the License.
|
|
14
|
-
* You may obtain a copy of the License at
|
|
15
|
-
*
|
|
16
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
-
*
|
|
18
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
-
* See the License for the specific language governing permissions and
|
|
22
|
-
* limitations under the License.
|
|
23
|
-
*/
|
|
24
|
-
/* eslint-disable-next-line no-console */ /* istanbul ignore next */
|
|
25
1
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
26
2
|
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
27
|
-
// Node modules
|
|
28
3
|
import os from 'node:os';
|
|
29
4
|
import path from 'node:path';
|
|
30
5
|
import EventEmitter from 'node:events';
|
|
31
|
-
// AnsiLogger module
|
|
32
6
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
33
7
|
import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
|
|
34
8
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
|
|
@@ -36,7 +10,6 @@ import { FabricIndex } from '@matter/types/datatype';
|
|
|
36
10
|
import { CommissioningOptions } from '@matter/types/commissioning';
|
|
37
11
|
import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
|
|
38
12
|
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
39
|
-
// @matterbridge
|
|
40
13
|
import { createZip, formatBytes, formatPercent, formatUptime, getParameter, hasParameter, inspectError, isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString, wait, withTimeout, } from '@matterbridge/utils';
|
|
41
14
|
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from '@matterbridge/types';
|
|
42
15
|
import { BroadcastServer } from '@matterbridge/thread';
|
|
@@ -63,8 +36,8 @@ export class Frontend extends EventEmitter {
|
|
|
63
36
|
this.log = new AnsiLogger({
|
|
64
37
|
logName: 'Frontend',
|
|
65
38
|
logNameColor: '\x1b[38;5;97m',
|
|
66
|
-
logTimestampFormat: 4
|
|
67
|
-
logLevel: hasParameter('debug') ? "debug"
|
|
39
|
+
logTimestampFormat: 4,
|
|
40
|
+
logLevel: hasParameter('debug') ? "debug" : "info",
|
|
68
41
|
});
|
|
69
42
|
this.server = new BroadcastServer('frontend', this.log);
|
|
70
43
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -75,7 +48,6 @@ export class Frontend extends EventEmitter {
|
|
|
75
48
|
}
|
|
76
49
|
async msgHandler(msg) {
|
|
77
50
|
if (this.server.isWorkerRequest(msg)) {
|
|
78
|
-
// istanbul ignore else
|
|
79
51
|
if (this.verbose)
|
|
80
52
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
81
53
|
switch (msg.type) {
|
|
@@ -127,13 +99,11 @@ export class Frontend extends EventEmitter {
|
|
|
127
99
|
this.server.respond({ ...msg, result: { success: true } });
|
|
128
100
|
break;
|
|
129
101
|
default:
|
|
130
|
-
// istanbul ignore next
|
|
131
102
|
if (this.verbose)
|
|
132
103
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
133
104
|
}
|
|
134
105
|
}
|
|
135
106
|
if (this.server.isWorkerResponse(msg) && msg.result) {
|
|
136
|
-
// istanbul ignore next
|
|
137
107
|
if (this.verbose)
|
|
138
108
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
139
109
|
switch (msg.type) {
|
|
@@ -177,55 +147,23 @@ export class Frontend extends EventEmitter {
|
|
|
177
147
|
this.port = port;
|
|
178
148
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
179
149
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
180
|
-
// Initialize multer with the upload directory
|
|
181
150
|
const multer = await import('multer');
|
|
182
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
151
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
183
152
|
const upload = multer.default({ dest: uploadDir });
|
|
184
|
-
// Create the express app that serves the frontend
|
|
185
153
|
const express = await import('express');
|
|
186
154
|
this.expressApp = express.default();
|
|
187
|
-
// Inject logging/debug wrapper for route/middleware registration
|
|
188
|
-
/*
|
|
189
|
-
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
190
|
-
for (const method of methods) {
|
|
191
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
|
-
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
193
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
-
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
195
|
-
try {
|
|
196
|
-
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
197
|
-
return original(path, ...rest);
|
|
198
|
-
} catch (err) {
|
|
199
|
-
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
200
|
-
throw err;
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
*/
|
|
205
|
-
// Log all requests to the server for debugging
|
|
206
|
-
/*
|
|
207
|
-
this.expressApp.use((req, res, next) => {
|
|
208
|
-
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
209
|
-
next();
|
|
210
|
-
});
|
|
211
|
-
*/
|
|
212
|
-
// Serve static files from 'frontend/build' directory
|
|
213
155
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'apps', 'frontend', 'build')));
|
|
214
|
-
// Create a WebSocket server and attach it to the http or https server
|
|
215
156
|
this.log.debug(`Creating WebSocketServer...`);
|
|
216
157
|
const ws = await import('ws');
|
|
217
158
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
218
159
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
219
160
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
220
161
|
const clientIp = request.socket.remoteAddress;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (this.matterbridge.getLogLevel() === "
|
|
225
|
-
callbackLogLevel = "
|
|
226
|
-
// istanbul ignore else
|
|
227
|
-
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
228
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
162
|
+
let callbackLogLevel = "notice";
|
|
163
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
164
|
+
callbackLogLevel = "info";
|
|
165
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
166
|
+
callbackLogLevel = "debug";
|
|
229
167
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
230
168
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
231
169
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -241,34 +179,23 @@ export class Frontend extends EventEmitter {
|
|
|
241
179
|
});
|
|
242
180
|
ws.on('close', () => {
|
|
243
181
|
this.log.info('WebSocket client disconnected');
|
|
244
|
-
// istanbul ignore else
|
|
245
182
|
if (this.webSocketServer?.clients.size === 0) {
|
|
246
183
|
AnsiLogger.setGlobalCallback(undefined);
|
|
247
184
|
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
248
185
|
this.authClients = [];
|
|
249
186
|
}
|
|
250
187
|
});
|
|
251
|
-
// istanbul ignore next
|
|
252
188
|
ws.on('error', (error) => {
|
|
253
|
-
// istanbul ignore next
|
|
254
189
|
this.log.error(`WebSocket client error: ${error}`);
|
|
255
190
|
});
|
|
256
191
|
});
|
|
257
192
|
this.webSocketServer.on('close', () => {
|
|
258
193
|
this.log.debug(`WebSocketServer closed`);
|
|
259
194
|
});
|
|
260
|
-
/* With { noServer: true } it never fires
|
|
261
|
-
this.webSocketServer.on('listening', () => {
|
|
262
|
-
this.log.info(`The WebSocketServer is listening`);
|
|
263
|
-
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
264
|
-
});
|
|
265
|
-
*/
|
|
266
|
-
// istanbul ignore next
|
|
267
195
|
this.webSocketServer.on('error', (ws, error) => {
|
|
268
196
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
269
197
|
});
|
|
270
198
|
if (!hasParameter('ssl')) {
|
|
271
|
-
// Create an HTTP server and attach the express app
|
|
272
199
|
const http = await import('node:http');
|
|
273
200
|
try {
|
|
274
201
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -279,54 +206,33 @@ export class Frontend extends EventEmitter {
|
|
|
279
206
|
this.emit('server_error', error);
|
|
280
207
|
return;
|
|
281
208
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const addr = this.httpServer?.address();
|
|
295
|
-
// istanbul ignore else
|
|
296
|
-
if (addr && typeof addr !== 'string') {
|
|
297
|
-
this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
298
|
-
}
|
|
299
|
-
// istanbul ignore else
|
|
300
|
-
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
301
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
302
|
-
// istanbul ignore else
|
|
303
|
-
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
304
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
305
|
-
this.listening = true;
|
|
306
|
-
this.emit('server_listening', 'http', this.port);
|
|
307
|
-
});
|
|
308
|
-
}
|
|
209
|
+
this.httpServer.listen(this.port, getParameter('bind'), () => {
|
|
210
|
+
const addr = this.httpServer?.address();
|
|
211
|
+
if (addr && typeof addr !== 'string') {
|
|
212
|
+
this.log.info(`The frontend http server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
213
|
+
}
|
|
214
|
+
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
215
|
+
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
216
|
+
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
217
|
+
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
218
|
+
this.listening = true;
|
|
219
|
+
this.emit('server_listening', 'http', this.port);
|
|
220
|
+
});
|
|
309
221
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
310
222
|
try {
|
|
311
|
-
// Only proceed for real WebSocket upgrades
|
|
312
|
-
// istanbul ignore next cause is only a safety check
|
|
313
223
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
314
224
|
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
315
225
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
316
226
|
return socket.destroy();
|
|
317
227
|
}
|
|
318
|
-
// Build a URL so we can read ?password=...
|
|
319
228
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
320
|
-
// Validate WebSocket password
|
|
321
229
|
const password = url.searchParams.get('password') ?? '';
|
|
322
230
|
if (password !== this.storedPassword) {
|
|
323
231
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
324
232
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
325
233
|
return socket.destroy();
|
|
326
234
|
}
|
|
327
|
-
// Complete the WebSocket handshake
|
|
328
235
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
329
|
-
// istanbul ignore else
|
|
330
236
|
if (req.socket.remoteAddress)
|
|
331
237
|
this.authClients.push(req.socket.remoteAddress);
|
|
332
238
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -334,7 +240,6 @@ export class Frontend extends EventEmitter {
|
|
|
334
240
|
});
|
|
335
241
|
}
|
|
336
242
|
catch (err) {
|
|
337
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
338
243
|
{
|
|
339
244
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
340
245
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -357,7 +262,6 @@ export class Frontend extends EventEmitter {
|
|
|
357
262
|
});
|
|
358
263
|
}
|
|
359
264
|
else {
|
|
360
|
-
// SSL is enabled, load the certificate and the private key
|
|
361
265
|
let cert;
|
|
362
266
|
let key;
|
|
363
267
|
let ca;
|
|
@@ -367,7 +271,6 @@ export class Frontend extends EventEmitter {
|
|
|
367
271
|
let httpsServerOptions = {};
|
|
368
272
|
const fs = await import('node:fs');
|
|
369
273
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
370
|
-
// Load the p12 certificate and the passphrase
|
|
371
274
|
try {
|
|
372
275
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
373
276
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -379,7 +282,7 @@ export class Frontend extends EventEmitter {
|
|
|
379
282
|
}
|
|
380
283
|
try {
|
|
381
284
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
382
|
-
passphrase = passphrase.trim();
|
|
285
|
+
passphrase = passphrase.trim();
|
|
383
286
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
384
287
|
}
|
|
385
288
|
catch (error) {
|
|
@@ -390,7 +293,6 @@ export class Frontend extends EventEmitter {
|
|
|
390
293
|
httpsServerOptions = { pfx, passphrase };
|
|
391
294
|
}
|
|
392
295
|
else {
|
|
393
|
-
// 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.
|
|
394
296
|
try {
|
|
395
297
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
396
298
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -420,10 +322,9 @@ export class Frontend extends EventEmitter {
|
|
|
420
322
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
421
323
|
}
|
|
422
324
|
if (hasParameter('mtls')) {
|
|
423
|
-
httpsServerOptions.requestCert = true;
|
|
424
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
325
|
+
httpsServerOptions.requestCert = true;
|
|
326
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
425
327
|
}
|
|
426
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
427
328
|
const https = await import('node:https');
|
|
428
329
|
try {
|
|
429
330
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -434,53 +335,32 @@ export class Frontend extends EventEmitter {
|
|
|
434
335
|
this.emit('server_error', error);
|
|
435
336
|
return;
|
|
436
337
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
this.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const addr = this.httpsServer?.address();
|
|
450
|
-
// istanbul ignore else
|
|
451
|
-
if (addr && typeof addr !== 'string') {
|
|
452
|
-
this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
453
|
-
}
|
|
454
|
-
// istanbul ignore else
|
|
455
|
-
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
456
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
457
|
-
// istanbul ignore else
|
|
458
|
-
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
459
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
460
|
-
this.listening = true;
|
|
461
|
-
this.emit('server_listening', 'https', this.port);
|
|
462
|
-
});
|
|
463
|
-
}
|
|
338
|
+
this.httpsServer.listen(this.port, getParameter('bind'), () => {
|
|
339
|
+
const addr = this.httpsServer?.address();
|
|
340
|
+
if (addr && typeof addr !== 'string') {
|
|
341
|
+
this.log.info(`The frontend https server is bound to ${addr.family} ${addr.address}:${addr.port}`);
|
|
342
|
+
}
|
|
343
|
+
if (this.matterbridge.systemInformation.ipv4Address !== '' && !getParameter('bind'))
|
|
344
|
+
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
345
|
+
if (this.matterbridge.systemInformation.ipv6Address !== '' && !getParameter('bind'))
|
|
346
|
+
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
347
|
+
this.listening = true;
|
|
348
|
+
this.emit('server_listening', 'https', this.port);
|
|
349
|
+
});
|
|
464
350
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
465
351
|
try {
|
|
466
|
-
// Only proceed for real WebSocket upgrades
|
|
467
|
-
// istanbul ignore next cause is only a safety check
|
|
468
352
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
469
353
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
470
354
|
return socket.destroy();
|
|
471
355
|
}
|
|
472
|
-
// Build a URL so we can read ?password=...
|
|
473
356
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
474
|
-
// Validate WebSocket password
|
|
475
357
|
const password = url.searchParams.get('password') ?? '';
|
|
476
358
|
if (password !== this.storedPassword) {
|
|
477
359
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
478
360
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
479
361
|
return socket.destroy();
|
|
480
362
|
}
|
|
481
|
-
// Complete the WebSocket handshake
|
|
482
363
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
483
|
-
// istanbul ignore else
|
|
484
364
|
if (req.socket.remoteAddress)
|
|
485
365
|
this.authClients.push(req.socket.remoteAddress);
|
|
486
366
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -488,7 +368,6 @@ export class Frontend extends EventEmitter {
|
|
|
488
368
|
});
|
|
489
369
|
}
|
|
490
370
|
catch (err) {
|
|
491
|
-
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
492
371
|
{
|
|
493
372
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
494
373
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -510,7 +389,6 @@ export class Frontend extends EventEmitter {
|
|
|
510
389
|
return;
|
|
511
390
|
});
|
|
512
391
|
}
|
|
513
|
-
// Subscribe to cli events
|
|
514
392
|
cliEmitter.removeAllListeners();
|
|
515
393
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
516
394
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -521,8 +399,6 @@ export class Frontend extends EventEmitter {
|
|
|
521
399
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
522
400
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
523
401
|
});
|
|
524
|
-
// Endpoint to validate login code
|
|
525
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
526
402
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
527
403
|
const { password } = req.body;
|
|
528
404
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -537,20 +413,17 @@ export class Frontend extends EventEmitter {
|
|
|
537
413
|
res.json({ valid: false });
|
|
538
414
|
}
|
|
539
415
|
});
|
|
540
|
-
// Endpoint to provide health check for docker
|
|
541
416
|
this.expressApp.get('/health', (req, res) => {
|
|
542
417
|
this.log.debug('Express received /health');
|
|
543
418
|
const healthStatus = {
|
|
544
|
-
status: 'ok',
|
|
545
|
-
uptime: process.uptime(),
|
|
546
|
-
timestamp: new Date().toISOString(),
|
|
419
|
+
status: 'ok',
|
|
420
|
+
uptime: process.uptime(),
|
|
421
|
+
timestamp: new Date().toISOString(),
|
|
547
422
|
};
|
|
548
423
|
res.status(200).json(healthStatus);
|
|
549
424
|
});
|
|
550
|
-
// Endpoint to provide memory usage details
|
|
551
425
|
this.expressApp.get('/memory', async (req, res) => {
|
|
552
426
|
this.log.debug('Express received /memory');
|
|
553
|
-
// Memory usage from process
|
|
554
427
|
const memoryUsageRaw = process.memoryUsage();
|
|
555
428
|
const memoryUsage = {
|
|
556
429
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -559,13 +432,10 @@ export class Frontend extends EventEmitter {
|
|
|
559
432
|
external: formatBytes(memoryUsageRaw.external),
|
|
560
433
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
561
434
|
};
|
|
562
|
-
// V8 heap statistics
|
|
563
435
|
const { default: v8 } = await import('node:v8');
|
|
564
436
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
565
437
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
566
|
-
// Format heapStats
|
|
567
438
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
568
|
-
// Format heapSpaces
|
|
569
439
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
570
440
|
...space,
|
|
571
441
|
space_size: formatBytes(space.space_size),
|
|
@@ -584,28 +454,24 @@ export class Frontend extends EventEmitter {
|
|
|
584
454
|
};
|
|
585
455
|
res.status(200).json(memoryReport);
|
|
586
456
|
});
|
|
587
|
-
// Endpoint to provide settings
|
|
588
457
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
589
458
|
this.log.debug('The frontend sent /api/settings');
|
|
590
459
|
if (!this.validateReq(req, res))
|
|
591
460
|
return;
|
|
592
461
|
res.json(await this.getApiSettings());
|
|
593
462
|
});
|
|
594
|
-
// Endpoint to provide plugins
|
|
595
463
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
596
464
|
this.log.debug('The frontend sent /api/plugins');
|
|
597
465
|
if (!this.validateReq(req, res))
|
|
598
466
|
return;
|
|
599
467
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
600
468
|
});
|
|
601
|
-
// Endpoint to provide devices
|
|
602
469
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
603
470
|
this.log.debug('The frontend sent /api/devices');
|
|
604
471
|
if (!this.validateReq(req, res))
|
|
605
472
|
return;
|
|
606
473
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
607
474
|
});
|
|
608
|
-
// Endpoint to view the matterbridge log
|
|
609
475
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
610
476
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
611
477
|
if (!this.validateReq(req, res))
|
|
@@ -621,7 +487,6 @@ export class Frontend extends EventEmitter {
|
|
|
621
487
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
622
488
|
}
|
|
623
489
|
});
|
|
624
|
-
// Endpoint to view the matter.js log
|
|
625
490
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
626
491
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
627
492
|
if (!this.validateReq(req, res))
|
|
@@ -637,7 +502,6 @@ export class Frontend extends EventEmitter {
|
|
|
637
502
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
638
503
|
}
|
|
639
504
|
});
|
|
640
|
-
// Endpoint to view the diagnostic.log
|
|
641
505
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
642
506
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
643
507
|
if (!this.validateReq(req, res))
|
|
@@ -650,13 +514,10 @@ export class Frontend extends EventEmitter {
|
|
|
650
514
|
res.send(data.slice(29));
|
|
651
515
|
}
|
|
652
516
|
catch (error) {
|
|
653
|
-
// istanbul ignore next
|
|
654
517
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
655
|
-
// istanbul ignore next
|
|
656
518
|
res.status(500).send('Error reading diagnostic log file.');
|
|
657
519
|
}
|
|
658
520
|
});
|
|
659
|
-
// Endpoint to download the diagnostic.log
|
|
660
521
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
661
522
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
662
523
|
if (!this.validateReq(req, res))
|
|
@@ -669,19 +530,16 @@ export class Frontend extends EventEmitter {
|
|
|
669
530
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
670
531
|
}
|
|
671
532
|
catch (error) {
|
|
672
|
-
// istanbul ignore next
|
|
673
533
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
674
534
|
}
|
|
675
535
|
res.type('text/plain');
|
|
676
536
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
677
|
-
/* istanbul ignore if */
|
|
678
537
|
if (error) {
|
|
679
538
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
680
539
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
681
540
|
}
|
|
682
541
|
});
|
|
683
542
|
});
|
|
684
|
-
// Endpoint to view the history.html
|
|
685
543
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
686
544
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
687
545
|
if (!this.validateReq(req, res))
|
|
@@ -697,7 +555,6 @@ export class Frontend extends EventEmitter {
|
|
|
697
555
|
res.status(500).send('Error reading history file.');
|
|
698
556
|
}
|
|
699
557
|
});
|
|
700
|
-
// Endpoint to download the history.html
|
|
701
558
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
702
559
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
703
560
|
if (!this.validateReq(req, res))
|
|
@@ -709,7 +566,6 @@ export class Frontend extends EventEmitter {
|
|
|
709
566
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
710
567
|
res.type('text/plain');
|
|
711
568
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
712
|
-
/* istanbul ignore if */
|
|
713
569
|
if (error) {
|
|
714
570
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
715
571
|
res.status(500).send('Error downloading history file');
|
|
@@ -721,7 +577,6 @@ export class Frontend extends EventEmitter {
|
|
|
721
577
|
res.status(500).send('Error reading history file.');
|
|
722
578
|
}
|
|
723
579
|
});
|
|
724
|
-
// Endpoint to view the shelly log
|
|
725
580
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
726
581
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
727
582
|
if (!this.validateReq(req, res))
|
|
@@ -737,7 +592,6 @@ export class Frontend extends EventEmitter {
|
|
|
737
592
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
738
593
|
}
|
|
739
594
|
});
|
|
740
|
-
// Endpoint to download the matterbridge log
|
|
741
595
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
742
596
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
743
597
|
if (!this.validateReq(req, res))
|
|
@@ -754,14 +608,12 @@ export class Frontend extends EventEmitter {
|
|
|
754
608
|
}
|
|
755
609
|
res.type('text/plain');
|
|
756
610
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
757
|
-
/* istanbul ignore if */
|
|
758
611
|
if (error) {
|
|
759
612
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
760
613
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
761
614
|
}
|
|
762
615
|
});
|
|
763
616
|
});
|
|
764
|
-
// Endpoint to download the matter log
|
|
765
617
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
766
618
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
767
619
|
if (!this.validateReq(req, res))
|
|
@@ -778,14 +630,12 @@ export class Frontend extends EventEmitter {
|
|
|
778
630
|
}
|
|
779
631
|
res.type('text/plain');
|
|
780
632
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
781
|
-
/* istanbul ignore if */
|
|
782
633
|
if (error) {
|
|
783
634
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
784
635
|
res.status(500).send('Error downloading the matter log file');
|
|
785
636
|
}
|
|
786
637
|
});
|
|
787
638
|
});
|
|
788
|
-
// Endpoint to download the shelly log
|
|
789
639
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
790
640
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
791
641
|
if (!this.validateReq(req, res))
|
|
@@ -802,103 +652,87 @@ export class Frontend extends EventEmitter {
|
|
|
802
652
|
}
|
|
803
653
|
res.type('text/plain');
|
|
804
654
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
805
|
-
/* istanbul ignore if */
|
|
806
655
|
if (error) {
|
|
807
656
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
808
657
|
res.status(500).send('Error downloading Shelly system log file');
|
|
809
658
|
}
|
|
810
659
|
});
|
|
811
660
|
});
|
|
812
|
-
// Endpoint to download the matterbridge storage directory
|
|
813
661
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
814
662
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
815
663
|
if (!this.validateReq(req, res))
|
|
816
664
|
return;
|
|
817
665
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
818
666
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
819
|
-
/* istanbul ignore if */
|
|
820
667
|
if (error) {
|
|
821
668
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
822
669
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
823
670
|
}
|
|
824
671
|
});
|
|
825
672
|
});
|
|
826
|
-
// Endpoint to download the matter storage file
|
|
827
673
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
828
674
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
829
675
|
if (!this.validateReq(req, res))
|
|
830
676
|
return;
|
|
831
677
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
832
678
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
833
|
-
/* istanbul ignore if */
|
|
834
679
|
if (error) {
|
|
835
680
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
836
681
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
837
682
|
}
|
|
838
683
|
});
|
|
839
684
|
});
|
|
840
|
-
// Endpoint to download the matterbridge plugin directory
|
|
841
685
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
842
686
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
843
687
|
if (!this.validateReq(req, res))
|
|
844
688
|
return;
|
|
845
689
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
846
690
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
847
|
-
/* istanbul ignore if */
|
|
848
691
|
if (error) {
|
|
849
692
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
850
693
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
851
694
|
}
|
|
852
695
|
});
|
|
853
696
|
});
|
|
854
|
-
// Endpoint to download the matterbridge plugin config files
|
|
855
697
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
856
698
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
857
699
|
if (!this.validateReq(req, res))
|
|
858
700
|
return;
|
|
859
701
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
860
702
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
861
|
-
/* istanbul ignore if */
|
|
862
703
|
if (error) {
|
|
863
704
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
864
705
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
865
706
|
}
|
|
866
707
|
});
|
|
867
708
|
});
|
|
868
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
869
709
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
870
710
|
this.log.debug('The frontend sent /api/download-backup');
|
|
871
711
|
if (!this.validateReq(req, res))
|
|
872
712
|
return;
|
|
873
713
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
874
|
-
/* istanbul ignore if */
|
|
875
714
|
if (error) {
|
|
876
715
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
877
716
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
878
717
|
}
|
|
879
718
|
});
|
|
880
719
|
});
|
|
881
|
-
// Endpoint to upload a package
|
|
882
720
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
883
721
|
if (!this.validateReq(req, res))
|
|
884
722
|
return;
|
|
885
723
|
const { filename } = req.body;
|
|
886
724
|
const file = req.file;
|
|
887
|
-
/* istanbul ignore if */
|
|
888
725
|
if (!file || !filename) {
|
|
889
726
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
890
727
|
res.status(400).send('Invalid request: file and filename are required');
|
|
891
728
|
return;
|
|
892
729
|
}
|
|
893
730
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
894
|
-
// Define the path where the plugin file will be saved
|
|
895
731
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
896
732
|
try {
|
|
897
|
-
// Move the uploaded file to the specified path
|
|
898
733
|
const fs = await import('node:fs');
|
|
899
734
|
await fs.promises.rename(file.path, filePath);
|
|
900
735
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
901
|
-
// Install the plugin package
|
|
902
736
|
if (filename.endsWith('.tgz')) {
|
|
903
737
|
const { spawnCommand } = await import('./spawn.js');
|
|
904
738
|
if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
|
|
@@ -926,7 +760,6 @@ export class Frontend extends EventEmitter {
|
|
|
926
760
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
927
761
|
}
|
|
928
762
|
});
|
|
929
|
-
// Fallback for routing (must be the last route)
|
|
930
763
|
this.expressApp.use((req, res) => {
|
|
931
764
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'apps', 'frontend', 'build');
|
|
932
765
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -937,16 +770,13 @@ export class Frontend extends EventEmitter {
|
|
|
937
770
|
async stop() {
|
|
938
771
|
this.log.debug('Stopping the frontend...');
|
|
939
772
|
const ws = await import('ws');
|
|
940
|
-
// Remove listeners from the express app
|
|
941
773
|
if (this.expressApp) {
|
|
942
774
|
this.expressApp.removeAllListeners();
|
|
943
775
|
this.expressApp = undefined;
|
|
944
776
|
this.log.debug('Frontend app closed successfully');
|
|
945
777
|
}
|
|
946
|
-
// Close the WebSocket server
|
|
947
778
|
if (this.webSocketServer) {
|
|
948
779
|
this.log.debug('Closing WebSocket server...');
|
|
949
|
-
// Close all active connections
|
|
950
780
|
this.webSocketServer.clients.forEach((client) => {
|
|
951
781
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
952
782
|
client.close();
|
|
@@ -954,9 +784,7 @@ export class Frontend extends EventEmitter {
|
|
|
954
784
|
});
|
|
955
785
|
await withTimeout(new Promise((resolve) => {
|
|
956
786
|
this.webSocketServer?.close((error) => {
|
|
957
|
-
// istanbul ignore if
|
|
958
787
|
if (error) {
|
|
959
|
-
// istanbul ignore next
|
|
960
788
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
961
789
|
}
|
|
962
790
|
else {
|
|
@@ -969,27 +797,8 @@ export class Frontend extends EventEmitter {
|
|
|
969
797
|
this.webSocketServer.removeAllListeners();
|
|
970
798
|
this.webSocketServer = undefined;
|
|
971
799
|
}
|
|
972
|
-
// Close the http server
|
|
973
800
|
if (this.httpServer) {
|
|
974
801
|
this.log.debug('Closing http server...');
|
|
975
|
-
/*
|
|
976
|
-
await withTimeout(
|
|
977
|
-
new Promise<void>((resolve) => {
|
|
978
|
-
this.httpServer?.close((error) => {
|
|
979
|
-
if (error) {
|
|
980
|
-
// istanbul ignore next
|
|
981
|
-
this.log.error(`Error closing http server: ${error}`);
|
|
982
|
-
} else {
|
|
983
|
-
this.log.debug('Http server closed successfully');
|
|
984
|
-
this.emit('server_stopped');
|
|
985
|
-
}
|
|
986
|
-
resolve();
|
|
987
|
-
});
|
|
988
|
-
}),
|
|
989
|
-
5000,
|
|
990
|
-
false,
|
|
991
|
-
);
|
|
992
|
-
*/
|
|
993
802
|
this.httpServer.close();
|
|
994
803
|
this.log.debug('Http server closed successfully');
|
|
995
804
|
this.listening = false;
|
|
@@ -998,27 +807,8 @@ export class Frontend extends EventEmitter {
|
|
|
998
807
|
this.httpServer = undefined;
|
|
999
808
|
this.log.debug('Frontend http server closed successfully');
|
|
1000
809
|
}
|
|
1001
|
-
// Close the https server
|
|
1002
810
|
if (this.httpsServer) {
|
|
1003
811
|
this.log.debug('Closing https server...');
|
|
1004
|
-
/*
|
|
1005
|
-
await withTimeout(
|
|
1006
|
-
new Promise<void>((resolve) => {
|
|
1007
|
-
this.httpsServer?.close((error) => {
|
|
1008
|
-
if (error) {
|
|
1009
|
-
// istanbul ignore next
|
|
1010
|
-
this.log.error(`Error closing https server: ${error}`);
|
|
1011
|
-
} else {
|
|
1012
|
-
this.log.debug('Https server closed successfully');
|
|
1013
|
-
this.emit('server_stopped');
|
|
1014
|
-
}
|
|
1015
|
-
resolve();
|
|
1016
|
-
});
|
|
1017
|
-
}),
|
|
1018
|
-
5000,
|
|
1019
|
-
false,
|
|
1020
|
-
);
|
|
1021
|
-
*/
|
|
1022
812
|
this.httpsServer.close();
|
|
1023
813
|
this.log.debug('Https server closed successfully');
|
|
1024
814
|
this.listening = false;
|
|
@@ -1029,13 +819,7 @@ export class Frontend extends EventEmitter {
|
|
|
1029
819
|
}
|
|
1030
820
|
this.log.debug('Frontend stopped successfully');
|
|
1031
821
|
}
|
|
1032
|
-
/**
|
|
1033
|
-
* Retrieves the api settings data.
|
|
1034
|
-
*
|
|
1035
|
-
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
1036
|
-
*/
|
|
1037
822
|
async getApiSettings() {
|
|
1038
|
-
// Update the variable system information properties
|
|
1039
823
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
1040
824
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
1041
825
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -1045,7 +829,6 @@ export class Frontend extends EventEmitter {
|
|
|
1045
829
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
1046
830
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
1047
831
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
1048
|
-
// Create the matterbridge information
|
|
1049
832
|
const info = {
|
|
1050
833
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
1051
834
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -1081,15 +864,9 @@ export class Frontend extends EventEmitter {
|
|
|
1081
864
|
};
|
|
1082
865
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
1083
866
|
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Retrieves the reachable attribute.
|
|
1086
|
-
*
|
|
1087
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
1088
|
-
* @returns {boolean} The reachable attribute.
|
|
1089
|
-
*/
|
|
1090
867
|
getReachability(device) {
|
|
1091
868
|
if (this.matterbridge.hasCleanupStarted)
|
|
1092
|
-
return false;
|
|
869
|
+
return false;
|
|
1093
870
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1094
871
|
return false;
|
|
1095
872
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -1100,15 +877,9 @@ export class Frontend extends EventEmitter {
|
|
|
1100
877
|
return true;
|
|
1101
878
|
return false;
|
|
1102
879
|
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Retrieves the power source attribute.
|
|
1105
|
-
*
|
|
1106
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1107
|
-
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1108
|
-
*/
|
|
1109
880
|
getPowerSource(endpoint) {
|
|
1110
881
|
if (this.matterbridge.hasCleanupStarted)
|
|
1111
|
-
return undefined;
|
|
882
|
+
return undefined;
|
|
1112
883
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1113
884
|
return undefined;
|
|
1114
885
|
const powerSource = (device) => {
|
|
@@ -1123,25 +894,16 @@ export class Frontend extends EventEmitter {
|
|
|
1123
894
|
}
|
|
1124
895
|
return;
|
|
1125
896
|
};
|
|
1126
|
-
// Root endpoint
|
|
1127
897
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1128
898
|
return powerSource(endpoint);
|
|
1129
|
-
// Child endpoints
|
|
1130
899
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1131
|
-
// istanbul ignore else
|
|
1132
900
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1133
901
|
return powerSource(child);
|
|
1134
902
|
}
|
|
1135
903
|
}
|
|
1136
|
-
/**
|
|
1137
|
-
* Retrieves the battery level attribute.
|
|
1138
|
-
*
|
|
1139
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1140
|
-
* @returns {number | undefined} The battery level attribute.
|
|
1141
|
-
*/
|
|
1142
904
|
getBatteryLevel(endpoint) {
|
|
1143
905
|
if (this.matterbridge.hasCleanupStarted)
|
|
1144
|
-
return undefined;
|
|
906
|
+
return undefined;
|
|
1145
907
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
1146
908
|
return undefined;
|
|
1147
909
|
const batteryLevel = (device) => {
|
|
@@ -1152,27 +914,16 @@ export class Frontend extends EventEmitter {
|
|
|
1152
914
|
}
|
|
1153
915
|
return undefined;
|
|
1154
916
|
};
|
|
1155
|
-
// Root endpoint
|
|
1156
917
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
1157
918
|
return batteryLevel(endpoint);
|
|
1158
|
-
// Child endpoints
|
|
1159
919
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1160
|
-
// istanbul ignore else
|
|
1161
920
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
1162
921
|
return batteryLevel(child);
|
|
1163
922
|
}
|
|
1164
923
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Retrieves the cluster text description from a given device.
|
|
1167
|
-
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1168
|
-
*
|
|
1169
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1170
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1171
|
-
*/
|
|
1172
924
|
getClusterTextFromDevice(device) {
|
|
1173
925
|
if (this.matterbridge.hasCleanupStarted)
|
|
1174
|
-
return '';
|
|
1175
|
-
// istanbul ignore else
|
|
926
|
+
return '';
|
|
1176
927
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
1177
928
|
return '';
|
|
1178
929
|
const getUserLabel = (device) => {
|
|
@@ -1182,7 +933,6 @@ export class Frontend extends EventEmitter {
|
|
|
1182
933
|
if (composed)
|
|
1183
934
|
return 'Composed: ' + composed.value;
|
|
1184
935
|
}
|
|
1185
|
-
// istanbul ignore next cause is not reachable
|
|
1186
936
|
return '';
|
|
1187
937
|
};
|
|
1188
938
|
const getFixedLabel = (device) => {
|
|
@@ -1192,13 +942,11 @@ export class Frontend extends EventEmitter {
|
|
|
1192
942
|
if (composed)
|
|
1193
943
|
return 'Composed: ' + composed.value;
|
|
1194
944
|
}
|
|
1195
|
-
// istanbul ignore next cause is not reacheable
|
|
1196
945
|
return '';
|
|
1197
946
|
};
|
|
1198
947
|
let attributes = '';
|
|
1199
948
|
let supportedModes = [];
|
|
1200
949
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1201
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
1202
950
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1203
951
|
return;
|
|
1204
952
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -1288,17 +1036,11 @@ export class Frontend extends EventEmitter {
|
|
|
1288
1036
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
1289
1037
|
attributes += `${getUserLabel(device)} `;
|
|
1290
1038
|
});
|
|
1291
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
1292
1039
|
return attributes.trimStart().trimEnd();
|
|
1293
1040
|
}
|
|
1294
|
-
/**
|
|
1295
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
1296
|
-
*
|
|
1297
|
-
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1298
|
-
*/
|
|
1299
1041
|
getPlugins() {
|
|
1300
1042
|
if (this.matterbridge.hasCleanupStarted)
|
|
1301
|
-
return [];
|
|
1043
|
+
return [];
|
|
1302
1044
|
const plugins = [];
|
|
1303
1045
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1304
1046
|
plugins.push({
|
|
@@ -1326,27 +1068,18 @@ export class Frontend extends EventEmitter {
|
|
|
1326
1068
|
schemaJson: plugin.schemaJson,
|
|
1327
1069
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1328
1070
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1329
|
-
// Childbridge mode specific data
|
|
1330
1071
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1331
1072
|
});
|
|
1332
1073
|
}
|
|
1333
1074
|
return plugins;
|
|
1334
1075
|
}
|
|
1335
|
-
/**
|
|
1336
|
-
* Retrieves the devices from Matterbridge.
|
|
1337
|
-
*
|
|
1338
|
-
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1339
|
-
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1340
|
-
*/
|
|
1341
1076
|
getDevices(pluginName) {
|
|
1342
1077
|
if (this.matterbridge.hasCleanupStarted)
|
|
1343
|
-
return [];
|
|
1078
|
+
return [];
|
|
1344
1079
|
const devices = [];
|
|
1345
1080
|
for (const device of this.matterbridge.devices.array()) {
|
|
1346
|
-
// Filter by pluginName if provided
|
|
1347
1081
|
if (pluginName && pluginName !== device.plugin)
|
|
1348
1082
|
continue;
|
|
1349
|
-
// Check if the device has the required properties
|
|
1350
1083
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1351
1084
|
continue;
|
|
1352
1085
|
devices.push({
|
|
@@ -1367,40 +1100,24 @@ export class Frontend extends EventEmitter {
|
|
|
1367
1100
|
}
|
|
1368
1101
|
return devices;
|
|
1369
1102
|
}
|
|
1370
|
-
/**
|
|
1371
|
-
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1372
|
-
*
|
|
1373
|
-
* Response for /api/clusters
|
|
1374
|
-
*
|
|
1375
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1376
|
-
* @param {number} endpointNumber - The endpoint number.
|
|
1377
|
-
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1378
|
-
*/
|
|
1379
1103
|
getClusters(pluginName, endpointNumber) {
|
|
1380
1104
|
if (this.matterbridge.hasCleanupStarted)
|
|
1381
|
-
return;
|
|
1105
|
+
return;
|
|
1382
1106
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1383
1107
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1384
1108
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1385
1109
|
return;
|
|
1386
1110
|
}
|
|
1387
|
-
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1388
|
-
// Get the device types from the main endpoint
|
|
1389
1111
|
const deviceTypes = [];
|
|
1390
1112
|
const clusters = [];
|
|
1391
1113
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1392
1114
|
deviceTypes.push(d.deviceType);
|
|
1393
1115
|
});
|
|
1394
|
-
// Get the clusters from the main endpoint
|
|
1395
1116
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1396
1117
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1397
1118
|
return;
|
|
1398
|
-
// istanbul ignore if cause is not reachable without the EveHistory cluster
|
|
1399
1119
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1400
1120
|
return;
|
|
1401
|
-
// console.log(
|
|
1402
|
-
// `${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}`,
|
|
1403
|
-
// );
|
|
1404
1121
|
clusters.push({
|
|
1405
1122
|
endpoint: endpoint.number.toString(),
|
|
1406
1123
|
number: endpoint.number,
|
|
@@ -1414,19 +1131,12 @@ export class Frontend extends EventEmitter {
|
|
|
1414
1131
|
attributeLocalValue: attributeValue,
|
|
1415
1132
|
});
|
|
1416
1133
|
});
|
|
1417
|
-
// Get the child endpoints
|
|
1418
1134
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1419
|
-
// if (childEndpoints.length === 0) {
|
|
1420
|
-
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1421
|
-
// }
|
|
1422
1135
|
childEndpoints.forEach((childEndpoint) => {
|
|
1423
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1424
1136
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1425
1137
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1426
1138
|
return;
|
|
1427
1139
|
}
|
|
1428
|
-
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1429
|
-
// Get the device types of the child endpoint
|
|
1430
1140
|
const deviceTypes = [];
|
|
1431
1141
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1432
1142
|
deviceTypes.push(d.deviceType);
|
|
@@ -1434,13 +1144,9 @@ export class Frontend extends EventEmitter {
|
|
|
1434
1144
|
childEndpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1435
1145
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1436
1146
|
return;
|
|
1437
|
-
// istanbul ignore if cause is not reachable without the EveHistory cluster
|
|
1438
1147
|
if (clusterName === 'EveHistory' &&
|
|
1439
1148
|
['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1440
1149
|
return;
|
|
1441
|
-
// console.log(
|
|
1442
|
-
// `${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}`,
|
|
1443
|
-
// );
|
|
1444
1150
|
clusters.push({
|
|
1445
1151
|
endpoint: childEndpoint.number.toString(),
|
|
1446
1152
|
number: childEndpoint.number,
|
|
@@ -1460,7 +1166,6 @@ export class Frontend extends EventEmitter {
|
|
|
1460
1166
|
async generateDiagnostic() {
|
|
1461
1167
|
this.log.debug('Generating diagnostic...');
|
|
1462
1168
|
const serverNodes = [];
|
|
1463
|
-
// istanbul ignore else
|
|
1464
1169
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1465
1170
|
if (this.matterbridge.serverNode)
|
|
1466
1171
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1471,7 +1176,6 @@ export class Frontend extends EventEmitter {
|
|
|
1471
1176
|
serverNodes.push(plugin.serverNode);
|
|
1472
1177
|
}
|
|
1473
1178
|
}
|
|
1474
|
-
// istanbul ignore next
|
|
1475
1179
|
for (const device of this.matterbridge.devices.array()) {
|
|
1476
1180
|
if (device.serverNode)
|
|
1477
1181
|
serverNodes.push(device.serverNode);
|
|
@@ -1495,15 +1199,8 @@ export class Frontend extends EventEmitter {
|
|
|
1495
1199
|
values: [...serverNodes],
|
|
1496
1200
|
})));
|
|
1497
1201
|
delete Logger.destinations.diagnostic;
|
|
1498
|
-
await wait(500);
|
|
1202
|
+
await wait(500);
|
|
1499
1203
|
}
|
|
1500
|
-
/**
|
|
1501
|
-
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1502
|
-
*
|
|
1503
|
-
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1504
|
-
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1505
|
-
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1506
|
-
*/
|
|
1507
1204
|
async wsMessageHandler(client, message) {
|
|
1508
1205
|
let data;
|
|
1509
1206
|
const sendResponse = (data) => {
|
|
@@ -1521,7 +1218,6 @@ export class Frontend extends EventEmitter {
|
|
|
1521
1218
|
client.send(JSON.stringify(data));
|
|
1522
1219
|
}
|
|
1523
1220
|
else {
|
|
1524
|
-
// istanbul ignore next cause is only a safety check
|
|
1525
1221
|
this.log.error('Cannot send api response, client not connected');
|
|
1526
1222
|
}
|
|
1527
1223
|
};
|
|
@@ -1530,7 +1226,7 @@ export class Frontend extends EventEmitter {
|
|
|
1530
1226
|
if (!isValidNumber(data.id) ||
|
|
1531
1227
|
!isValidString(data.dst) ||
|
|
1532
1228
|
!isValidString(data.src) ||
|
|
1533
|
-
!isValidString(data.method)
|
|
1229
|
+
!isValidString(data.method) ||
|
|
1534
1230
|
data.dst !== 'Matterbridge') {
|
|
1535
1231
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1536
1232
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
@@ -1588,22 +1284,7 @@ export class Frontend extends EventEmitter {
|
|
|
1588
1284
|
}
|
|
1589
1285
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1590
1286
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1591
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1592
|
-
/*
|
|
1593
|
-
const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
|
|
1594
|
-
if (plugin) {
|
|
1595
|
-
this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
|
|
1596
|
-
await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
|
|
1597
|
-
this.wssSendRestartRequired();
|
|
1598
|
-
this.wssSendRefreshRequired('plugins');
|
|
1599
|
-
this.wssSendRefreshRequired('devices');
|
|
1600
|
-
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1601
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1602
|
-
} else {
|
|
1603
|
-
this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
|
|
1604
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
1605
|
-
}
|
|
1606
|
-
*/
|
|
1287
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1607
1288
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1608
1289
|
if (plugin) {
|
|
1609
1290
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1616,7 +1297,7 @@ export class Frontend extends EventEmitter {
|
|
|
1616
1297
|
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1617
1298
|
return;
|
|
1618
1299
|
})
|
|
1619
|
-
.catch(
|
|
1300
|
+
.catch((_error) => { });
|
|
1620
1301
|
}
|
|
1621
1302
|
else {
|
|
1622
1303
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
@@ -1630,10 +1311,6 @@ export class Frontend extends EventEmitter {
|
|
|
1630
1311
|
}
|
|
1631
1312
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1632
1313
|
this.log.debug(`Removing plugin ${data.params.pluginName}...`);
|
|
1633
|
-
/*
|
|
1634
|
-
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);
|
|
1635
|
-
await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
|
|
1636
|
-
*/
|
|
1637
1314
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1638
1315
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1639
1316
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1661,11 +1338,9 @@ export class Frontend extends EventEmitter {
|
|
|
1661
1338
|
this.wssSendSnackbarMessage(`Enabled plugin ${data.params.pluginName}`, 5, 'success');
|
|
1662
1339
|
setImmediate(async () => {
|
|
1663
1340
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
1664
|
-
// @ts-expect-error Accessing private method
|
|
1665
1341
|
if (plugin.serverNode)
|
|
1666
1342
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1667
1343
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1668
|
-
// @ts-expect-error Accessing private method
|
|
1669
1344
|
await this.matterbridge.startServerNode(device.serverNode);
|
|
1670
1345
|
this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
|
|
1671
1346
|
this.wssSendRefreshRequired('plugins');
|
|
@@ -1679,16 +1354,12 @@ export class Frontend extends EventEmitter {
|
|
|
1679
1354
|
return;
|
|
1680
1355
|
}
|
|
1681
1356
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1682
|
-
// Stop server nodes devices first
|
|
1683
1357
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode)) {
|
|
1684
|
-
// @ts-expect-error Accessing private method
|
|
1685
1358
|
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1686
1359
|
device.serverNode = undefined;
|
|
1687
1360
|
}
|
|
1688
|
-
// Then shutdown plugin removing devices, disable it and stop plugin server node
|
|
1689
1361
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
1690
1362
|
await this.matterbridge.plugins.disable(data.params.pluginName);
|
|
1691
|
-
// @ts-expect-error Accessing private method
|
|
1692
1363
|
if (plugin.serverNode)
|
|
1693
1364
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1694
1365
|
plugin.serverNode = undefined;
|
|
@@ -1705,37 +1376,30 @@ export class Frontend extends EventEmitter {
|
|
|
1705
1376
|
this.wssSendSnackbarMessage(`Restarting plugin ${data.params.pluginName}`, 5, 'info');
|
|
1706
1377
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1707
1378
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1708
|
-
// Stop server nodes
|
|
1709
1379
|
if (plugin.serverNode) {
|
|
1710
|
-
// @ts-expect-error Accessing private method
|
|
1711
1380
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1712
1381
|
plugin.serverNode = undefined;
|
|
1713
1382
|
}
|
|
1714
1383
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name)) {
|
|
1715
|
-
// @ts-expect-error Accessing private method
|
|
1716
1384
|
if (device.serverNode)
|
|
1717
1385
|
await this.matterbridge.stopServerNode(device.serverNode);
|
|
1718
1386
|
device.serverNode = undefined;
|
|
1719
1387
|
this.log.debug(`Removing device ${device.deviceName} from plugin ${plugin.name}`);
|
|
1720
1388
|
this.matterbridge.devices.remove(device);
|
|
1721
1389
|
}
|
|
1722
|
-
// @ts-expect-error Accessing private method
|
|
1723
1390
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1724
1391
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1725
1392
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1726
|
-
plugin.restartRequired = false;
|
|
1393
|
+
plugin.restartRequired = false;
|
|
1727
1394
|
let needRestart = 0;
|
|
1728
1395
|
for (const plugin of this.matterbridge.plugins) {
|
|
1729
1396
|
if (plugin.restartRequired)
|
|
1730
1397
|
needRestart++;
|
|
1731
1398
|
}
|
|
1732
1399
|
if (needRestart === 0)
|
|
1733
|
-
this.wssSendRestartNotRequired(true);
|
|
1734
|
-
// Start server nodes
|
|
1735
|
-
// @ts-expect-error Accessing private method
|
|
1400
|
+
this.wssSendRestartNotRequired(true);
|
|
1736
1401
|
if (plugin.serverNode)
|
|
1737
1402
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1738
|
-
// @ts-expect-error Accessing private method
|
|
1739
1403
|
for (const device of this.matterbridge.devices.array().filter((d) => d.plugin === plugin.name && d.serverNode))
|
|
1740
1404
|
await this.matterbridge.startServerNode(device.serverNode);
|
|
1741
1405
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1767,54 +1431,18 @@ export class Frontend extends EventEmitter {
|
|
|
1767
1431
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1768
1432
|
}
|
|
1769
1433
|
else if (data.method === '/api/shellysysupdate') {
|
|
1770
|
-
/*
|
|
1771
|
-
const { triggerShellySysUpdate } = await import('./shelly.js');
|
|
1772
|
-
triggerShellySysUpdate(this.matterbridge);
|
|
1773
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1774
|
-
*/
|
|
1775
1434
|
}
|
|
1776
1435
|
else if (data.method === '/api/shellymainupdate') {
|
|
1777
|
-
/*
|
|
1778
|
-
const { triggerShellyMainUpdate } = await import('./shelly.js');
|
|
1779
|
-
triggerShellyMainUpdate(this.matterbridge);
|
|
1780
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1781
|
-
*/
|
|
1782
1436
|
}
|
|
1783
1437
|
else if (data.method === '/api/shellycreatesystemlog') {
|
|
1784
|
-
/*
|
|
1785
|
-
const { createShellySystemLog } = await import('./shelly.js');
|
|
1786
|
-
createShellySystemLog(this.matterbridge);
|
|
1787
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1788
|
-
*/
|
|
1789
1438
|
}
|
|
1790
1439
|
else if (data.method === '/api/shellynetconfig') {
|
|
1791
|
-
/*
|
|
1792
|
-
this.log.debug('/api/shellynetconfig:', data.params);
|
|
1793
|
-
const { triggerShellyChangeIp } = await import('./shelly.js');
|
|
1794
|
-
triggerShellyChangeIp(this.matterbridge, data.params as { type: 'static' | 'dhcp'; ip: string; subnet: string; gateway: string; dns: string });
|
|
1795
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1796
|
-
*/
|
|
1797
1440
|
}
|
|
1798
1441
|
else if (data.method === '/api/softreset') {
|
|
1799
|
-
/*
|
|
1800
|
-
const { triggerShellySoftReset } = await import('./shelly.js');
|
|
1801
|
-
triggerShellySoftReset(this.matterbridge);
|
|
1802
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1803
|
-
*/
|
|
1804
1442
|
}
|
|
1805
1443
|
else if (data.method === '/api/hardreset') {
|
|
1806
|
-
/*
|
|
1807
|
-
const { triggerShellyHardReset } = await import('./shelly.js');
|
|
1808
|
-
triggerShellyHardReset(this.matterbridge);
|
|
1809
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1810
|
-
*/
|
|
1811
1444
|
}
|
|
1812
1445
|
else if (data.method === '/api/reboot') {
|
|
1813
|
-
/*
|
|
1814
|
-
const { triggerShellyReboot } = await import('./shelly.js');
|
|
1815
|
-
triggerShellyReboot(this.matterbridge);
|
|
1816
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1817
|
-
*/
|
|
1818
1446
|
}
|
|
1819
1447
|
else if (data.method === '/api/restart') {
|
|
1820
1448
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
@@ -1954,7 +1582,6 @@ export class Frontend extends EventEmitter {
|
|
|
1954
1582
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
|
|
1955
1583
|
return;
|
|
1956
1584
|
}
|
|
1957
|
-
// istanbul ignore next
|
|
1958
1585
|
const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1959
1586
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
|
|
1960
1587
|
}
|
|
@@ -1968,7 +1595,6 @@ export class Frontend extends EventEmitter {
|
|
|
1968
1595
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
|
|
1969
1596
|
return;
|
|
1970
1597
|
}
|
|
1971
|
-
// istanbul ignore next
|
|
1972
1598
|
const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1973
1599
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
|
|
1974
1600
|
}
|
|
@@ -2026,22 +1652,22 @@ export class Frontend extends EventEmitter {
|
|
|
2026
1652
|
if (isValidString(data.params.value, 4)) {
|
|
2027
1653
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
2028
1654
|
if (data.params.value === 'Debug') {
|
|
2029
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1655
|
+
await this.matterbridge.setLogLevel("debug");
|
|
2030
1656
|
}
|
|
2031
1657
|
else if (data.params.value === 'Info') {
|
|
2032
|
-
await this.matterbridge.setLogLevel("info"
|
|
1658
|
+
await this.matterbridge.setLogLevel("info");
|
|
2033
1659
|
}
|
|
2034
1660
|
else if (data.params.value === 'Notice') {
|
|
2035
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1661
|
+
await this.matterbridge.setLogLevel("notice");
|
|
2036
1662
|
}
|
|
2037
1663
|
else if (data.params.value === 'Warn') {
|
|
2038
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1664
|
+
await this.matterbridge.setLogLevel("warn");
|
|
2039
1665
|
}
|
|
2040
1666
|
else if (data.params.value === 'Error') {
|
|
2041
|
-
await this.matterbridge.setLogLevel("error"
|
|
1667
|
+
await this.matterbridge.setLogLevel("error");
|
|
2042
1668
|
}
|
|
2043
1669
|
else if (data.params.value === 'Fatal') {
|
|
2044
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1670
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
2045
1671
|
}
|
|
2046
1672
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2047
1673
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -2052,7 +1678,6 @@ export class Frontend extends EventEmitter {
|
|
|
2052
1678
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
2053
1679
|
this.matterbridge.fileLogger = data.params.value;
|
|
2054
1680
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
2055
|
-
// Create the file logger for matterbridge
|
|
2056
1681
|
if (data.params.value)
|
|
2057
1682
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
2058
1683
|
else
|
|
@@ -2082,12 +1707,11 @@ export class Frontend extends EventEmitter {
|
|
|
2082
1707
|
Logger.level = MatterLogLevel.FATAL;
|
|
2083
1708
|
}
|
|
2084
1709
|
this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1710
|
+
let callbackLogLevel = "notice";
|
|
1711
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
1712
|
+
callbackLogLevel = "info";
|
|
1713
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
1714
|
+
callbackLogLevel = "debug";
|
|
2091
1715
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
2092
1716
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
2093
1717
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -2139,7 +1763,6 @@ export class Frontend extends EventEmitter {
|
|
|
2139
1763
|
}
|
|
2140
1764
|
break;
|
|
2141
1765
|
case 'setmatterport':
|
|
2142
|
-
// eslint-disable-next-line no-case-declarations
|
|
2143
1766
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2144
1767
|
if (isValidNumber(port, 5540, 5600)) {
|
|
2145
1768
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -2159,7 +1782,6 @@ export class Frontend extends EventEmitter {
|
|
|
2159
1782
|
}
|
|
2160
1783
|
break;
|
|
2161
1784
|
case 'setmatterdiscriminator':
|
|
2162
|
-
// eslint-disable-next-line no-case-declarations
|
|
2163
1785
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2164
1786
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
2165
1787
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -2185,7 +1807,6 @@ export class Frontend extends EventEmitter {
|
|
|
2185
1807
|
}
|
|
2186
1808
|
break;
|
|
2187
1809
|
case 'setmatterpasscode':
|
|
2188
|
-
// eslint-disable-next-line no-case-declarations
|
|
2189
1810
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
2190
1811
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
2191
1812
|
this.matterbridge.passcode = passcode;
|
|
@@ -2237,19 +1858,15 @@ export class Frontend extends EventEmitter {
|
|
|
2237
1858
|
return;
|
|
2238
1859
|
}
|
|
2239
1860
|
const config = plugin.configJson;
|
|
2240
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2241
1861
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2242
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2243
1862
|
if (select === 'serial')
|
|
2244
1863
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
2245
1864
|
if (select === 'name')
|
|
2246
1865
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
2247
1866
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2248
|
-
// Remove postfix from the serial if it exists
|
|
2249
1867
|
if (config.postfix) {
|
|
2250
1868
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2251
1869
|
}
|
|
2252
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
2253
1870
|
if (isValidArray(config.whiteList, 1)) {
|
|
2254
1871
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
2255
1872
|
config.whiteList.push(data.params.serial);
|
|
@@ -2258,7 +1875,6 @@ export class Frontend extends EventEmitter {
|
|
|
2258
1875
|
config.whiteList.push(data.params.name);
|
|
2259
1876
|
}
|
|
2260
1877
|
}
|
|
2261
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
2262
1878
|
if (isValidArray(config.blackList, 1)) {
|
|
2263
1879
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
2264
1880
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -2289,9 +1905,7 @@ export class Frontend extends EventEmitter {
|
|
|
2289
1905
|
return;
|
|
2290
1906
|
}
|
|
2291
1907
|
const config = plugin.configJson;
|
|
2292
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2293
1908
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2294
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
2295
1909
|
if (select === 'serial')
|
|
2296
1910
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
2297
1911
|
if (select === 'name')
|
|
@@ -2300,7 +1914,6 @@ export class Frontend extends EventEmitter {
|
|
|
2300
1914
|
if (config.postfix) {
|
|
2301
1915
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
2302
1916
|
}
|
|
2303
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
2304
1917
|
if (isValidArray(config.whiteList, 1)) {
|
|
2305
1918
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
2306
1919
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -2309,7 +1922,6 @@ export class Frontend extends EventEmitter {
|
|
|
2309
1922
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
2310
1923
|
}
|
|
2311
1924
|
}
|
|
2312
|
-
// Add the serial to the blackList
|
|
2313
1925
|
if (isValidArray(config.blackList)) {
|
|
2314
1926
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
2315
1927
|
config.blackList.push(data.params.serial);
|
|
@@ -2332,7 +1944,6 @@ export class Frontend extends EventEmitter {
|
|
|
2332
1944
|
}
|
|
2333
1945
|
}
|
|
2334
1946
|
else {
|
|
2335
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2336
1947
|
const localData = data;
|
|
2337
1948
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
2338
1949
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -2342,46 +1953,23 @@ export class Frontend extends EventEmitter {
|
|
|
2342
1953
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
2343
1954
|
}
|
|
2344
1955
|
}
|
|
2345
|
-
/**
|
|
2346
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2347
|
-
*
|
|
2348
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2349
|
-
* @param {string} time - The time string of the message
|
|
2350
|
-
* @param {string} name - The logger name of the message
|
|
2351
|
-
* @param {string} message - The content of the message.
|
|
2352
|
-
*
|
|
2353
|
-
* @remarks
|
|
2354
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2355
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2356
|
-
* The function sends the message to all connected clients.
|
|
2357
|
-
*/
|
|
2358
1956
|
wssSendLogMessage(level, time, name, message) {
|
|
2359
1957
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2360
1958
|
return;
|
|
2361
1959
|
if (!level || !time || !name || !message)
|
|
2362
1960
|
return;
|
|
2363
|
-
// Remove ANSI escape codes from the message
|
|
2364
|
-
// eslint-disable-next-line no-control-regex
|
|
2365
1961
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2366
|
-
// Remove leading asterisks from the message
|
|
2367
1962
|
message = message.replace(/^\*+/, '');
|
|
2368
|
-
// Replace all occurrences of \t and \n
|
|
2369
1963
|
message = message.replace(/[\t\n]/g, '');
|
|
2370
|
-
// Remove non-printable characters
|
|
2371
|
-
// eslint-disable-next-line no-control-regex
|
|
2372
1964
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2373
|
-
// Replace all occurrences of \" with "
|
|
2374
1965
|
message = message.replace(/\\"/g, '"');
|
|
2375
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2376
1966
|
const maxContinuousLength = 100;
|
|
2377
1967
|
const keepStartLength = 20;
|
|
2378
1968
|
const keepEndLength = 20;
|
|
2379
|
-
// Split the message into words
|
|
2380
1969
|
if (level !== 'spawn') {
|
|
2381
1970
|
message = message
|
|
2382
1971
|
.split(' ')
|
|
2383
1972
|
.map((word) => {
|
|
2384
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2385
1973
|
if (word.length > maxContinuousLength) {
|
|
2386
1974
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2387
1975
|
}
|
|
@@ -2389,34 +1977,14 @@ export class Frontend extends EventEmitter {
|
|
|
2389
1977
|
})
|
|
2390
1978
|
.join(' ');
|
|
2391
1979
|
}
|
|
2392
|
-
// Send the message to all connected clients
|
|
2393
1980
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2394
1981
|
}
|
|
2395
|
-
/**
|
|
2396
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2397
|
-
*
|
|
2398
|
-
* @param {string} changed - The changed value.
|
|
2399
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2400
|
-
* possible values for changed:
|
|
2401
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2402
|
-
* - 'plugins'
|
|
2403
|
-
* - 'devices'
|
|
2404
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2405
|
-
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2406
|
-
*/
|
|
2407
1982
|
wssSendRefreshRequired(changed, params) {
|
|
2408
1983
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2409
1984
|
return;
|
|
2410
1985
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2411
|
-
// Send the message to all connected clients
|
|
2412
1986
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2413
1987
|
}
|
|
2414
|
-
/**
|
|
2415
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2416
|
-
*
|
|
2417
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2418
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2419
|
-
*/
|
|
2420
1988
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2421
1989
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2422
1990
|
return;
|
|
@@ -2425,14 +1993,8 @@ export class Frontend extends EventEmitter {
|
|
|
2425
1993
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
2426
1994
|
if (snackbar === true)
|
|
2427
1995
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2428
|
-
// Send the message to all connected clients
|
|
2429
1996
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2430
1997
|
}
|
|
2431
|
-
/**
|
|
2432
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2433
|
-
*
|
|
2434
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2435
|
-
*/
|
|
2436
1998
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2437
1999
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2438
2000
|
return;
|
|
@@ -2440,35 +2002,20 @@ export class Frontend extends EventEmitter {
|
|
|
2440
2002
|
this.matterbridge.restartRequired = false;
|
|
2441
2003
|
if (snackbar === true)
|
|
2442
2004
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2443
|
-
// Send the message to all connected clients
|
|
2444
2005
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2445
2006
|
}
|
|
2446
|
-
/**
|
|
2447
|
-
* Sends a need to update WebSocket message to all connected clients.
|
|
2448
|
-
*
|
|
2449
|
-
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2450
|
-
*/
|
|
2451
2007
|
wssSendUpdateRequired(devVersion = false) {
|
|
2452
2008
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2453
2009
|
return;
|
|
2454
2010
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2455
2011
|
this.matterbridge.updateRequired = true;
|
|
2456
|
-
// Send the message to all connected clients
|
|
2457
2012
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2458
2013
|
}
|
|
2459
|
-
/**
|
|
2460
|
-
* Sends a cpu update message to all connected clients.
|
|
2461
|
-
*
|
|
2462
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2463
|
-
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2464
|
-
*/
|
|
2465
2014
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
2466
2015
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2467
2016
|
return;
|
|
2468
|
-
// istanbul ignore else
|
|
2469
2017
|
if (hasParameter('debug'))
|
|
2470
2018
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2471
|
-
// Send the message to all connected clients
|
|
2472
2019
|
this.wssBroadcastMessage({
|
|
2473
2020
|
id: 0,
|
|
2474
2021
|
src: 'Matterbridge',
|
|
@@ -2478,24 +2025,11 @@ export class Frontend extends EventEmitter {
|
|
|
2478
2025
|
response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 },
|
|
2479
2026
|
});
|
|
2480
2027
|
}
|
|
2481
|
-
/**
|
|
2482
|
-
* Sends a memory update message to all connected clients.
|
|
2483
|
-
*
|
|
2484
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2485
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2486
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2487
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2488
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2489
|
-
* @param {string} external - The external memory in bytes.
|
|
2490
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2491
|
-
*/
|
|
2492
2028
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2493
2029
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2494
2030
|
return;
|
|
2495
|
-
// istanbul ignore else
|
|
2496
2031
|
if (hasParameter('debug'))
|
|
2497
2032
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2498
|
-
// Send the message to all connected clients
|
|
2499
2033
|
this.wssBroadcastMessage({
|
|
2500
2034
|
id: 0,
|
|
2501
2035
|
src: 'Matterbridge',
|
|
@@ -2505,73 +2039,29 @@ export class Frontend extends EventEmitter {
|
|
|
2505
2039
|
response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers },
|
|
2506
2040
|
});
|
|
2507
2041
|
}
|
|
2508
|
-
/**
|
|
2509
|
-
* Sends an uptime update message to all connected clients.
|
|
2510
|
-
*
|
|
2511
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2512
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2513
|
-
*/
|
|
2514
2042
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2515
2043
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2516
2044
|
return;
|
|
2517
|
-
// istanbul ignore else
|
|
2518
2045
|
if (hasParameter('debug'))
|
|
2519
2046
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2520
|
-
// Send the message to all connected clients
|
|
2521
2047
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2522
2048
|
}
|
|
2523
|
-
/**
|
|
2524
|
-
* Sends an open snackbar message to all connected clients.
|
|
2525
|
-
*
|
|
2526
|
-
* @param {string} message - The message to send.
|
|
2527
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2528
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2529
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2530
|
-
*
|
|
2531
|
-
* @remarks
|
|
2532
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2533
|
-
*/
|
|
2534
2049
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2535
2050
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2536
2051
|
return;
|
|
2537
2052
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2538
|
-
// Send the message to all connected clients
|
|
2539
2053
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2540
2054
|
}
|
|
2541
|
-
/**
|
|
2542
|
-
* Sends a close snackbar message to all connected clients.
|
|
2543
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2544
|
-
*
|
|
2545
|
-
* @param {string} message - The message to send.
|
|
2546
|
-
*/
|
|
2547
2055
|
wssSendCloseSnackbarMessage(message) {
|
|
2548
2056
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2549
2057
|
return;
|
|
2550
2058
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2551
|
-
// Send the message to all connected clients
|
|
2552
2059
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2553
2060
|
}
|
|
2554
|
-
/**
|
|
2555
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2556
|
-
*
|
|
2557
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2558
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2559
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2560
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2561
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2562
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2563
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2564
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2565
|
-
*
|
|
2566
|
-
* @remarks
|
|
2567
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2568
|
-
* with the updated attribute information.
|
|
2569
|
-
*/
|
|
2570
2061
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2571
2062
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2572
2063
|
return;
|
|
2573
2064
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2574
|
-
// Send the message to all connected clients
|
|
2575
2065
|
this.wssBroadcastMessage({
|
|
2576
2066
|
id: 0,
|
|
2577
2067
|
src: 'Matterbridge',
|
|
@@ -2581,25 +2071,16 @@ export class Frontend extends EventEmitter {
|
|
|
2581
2071
|
response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value },
|
|
2582
2072
|
});
|
|
2583
2073
|
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Sends a message to all connected clients.
|
|
2586
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2587
|
-
*
|
|
2588
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2589
|
-
*/
|
|
2590
2074
|
wssBroadcastMessage(msg) {
|
|
2591
2075
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2592
2076
|
return;
|
|
2593
|
-
// Send the message to all connected clients
|
|
2594
2077
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2595
2078
|
if (msg.method !== 'log')
|
|
2596
2079
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
2597
2080
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2598
|
-
// istanbul ignore else
|
|
2599
2081
|
if (client.readyState === client.OPEN) {
|
|
2600
2082
|
client.send(stringifiedMsg);
|
|
2601
2083
|
}
|
|
2602
2084
|
});
|
|
2603
2085
|
}
|
|
2604
2086
|
}
|
|
2605
|
-
//# sourceMappingURL=frontend.js.map
|