matterbridge 3.4.4-dev-20251217-8b8b1cd → 3.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/broadcastServer.d.ts +144 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +117 -0
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +841 -0
- package/dist/broadcastServerTypes.d.ts.map +1 -0
- package/dist/broadcastServerTypes.js +24 -0
- package/dist/broadcastServerTypes.js.map +1 -0
- package/dist/cli.d.ts +30 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -1
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +50 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +37 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +48 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +38 -0
- package/dist/cliHistory.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/deviceManager.d.ts +135 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +113 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +61 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +56 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +76 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +43 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +24 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +245 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +485 -38
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +529 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/jestutils/export.d.ts +2 -0
- package/dist/jestutils/export.d.ts.map +1 -0
- package/dist/jestutils/export.js +1 -0
- package/dist/jestutils/export.js.map +1 -0
- package/dist/jestutils/jestHelpers.d.ts +345 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +371 -14
- package/dist/jestutils/jestHelpers.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterNode.d.ts +342 -0
- package/dist/matterNode.d.ts.map +1 -0
- package/dist/matterNode.js +369 -8
- package/dist/matterNode.js.map +1 -0
- package/dist/matterbridge.d.ts +505 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +829 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +41 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +38 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +2404 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +698 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +635 -14
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +41 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +38 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1507 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1457 -53
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +787 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +483 -20
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgeEndpointTypes.d.ts +166 -0
- package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
- package/dist/matterbridgeEndpointTypes.js +25 -0
- package/dist/matterbridgeEndpointTypes.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +539 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +451 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +252 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +26 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +372 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +341 -5
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +181 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +178 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +84 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +93 -1
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +101 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +66 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +60 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +35 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -0
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +45 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +42 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +13 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/format.d.ts +53 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +49 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hex.d.ts +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/inspector.d.ts +87 -0
- package/dist/utils/inspector.d.ts.map +1 -0
- package/dist/utils/inspector.js +69 -1
- package/dist/utils/inspector.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +111 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +96 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +33 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +71 -1
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/tracker.d.ts +108 -0
- package/dist/utils/tracker.d.ts.map +1 -0
- package/dist/utils/tracker.js +64 -1
- package/dist/utils/tracker.js.map +1 -0
- package/dist/utils/wait.d.ts +54 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/dist/workerGlobalPrefix.d.ts +25 -0
- package/dist/workerGlobalPrefix.d.ts.map +1 -0
- package/dist/workerGlobalPrefix.js +37 -5
- package/dist/workerGlobalPrefix.js.map +1 -0
- package/dist/workerTypes.d.ts +52 -0
- package/dist/workerTypes.d.ts.map +1 -0
- package/dist/workerTypes.js +24 -0
- package/dist/workerTypes.js.map +1 -0
- package/dist/workers.d.ts +69 -0
- package/dist/workers.d.ts.map +1 -0
- package/dist/workers.js +68 -4
- package/dist/workers.js.map +1 -0
- package/frontend/package-lock.json +13 -31
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Frontend.
|
|
3
|
+
*
|
|
4
|
+
* @file frontend.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2025-01-13
|
|
7
|
+
* @version 1.3.3
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
/* eslint-disable-next-line no-console */ /* istanbul ignore next */
|
|
1
25
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
2
26
|
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
27
|
+
// Node modules
|
|
3
28
|
import os from 'node:os';
|
|
4
29
|
import path from 'node:path';
|
|
5
30
|
import EventEmitter from 'node:events';
|
|
31
|
+
// AnsiLogger module
|
|
6
32
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
7
33
|
import { Logger, Diagnostic, LogDestination, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/general';
|
|
8
34
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
|
|
@@ -37,7 +63,7 @@ export class Frontend extends EventEmitter {
|
|
|
37
63
|
constructor(matterbridge) {
|
|
38
64
|
super();
|
|
39
65
|
this.matterbridge = matterbridge;
|
|
40
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
66
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
41
67
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
42
68
|
this.server = new BroadcastServer('frontend', this.log);
|
|
43
69
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
@@ -48,6 +74,7 @@ export class Frontend extends EventEmitter {
|
|
|
48
74
|
}
|
|
49
75
|
async msgHandler(msg) {
|
|
50
76
|
if (this.server.isWorkerRequest(msg)) {
|
|
77
|
+
// istanbul ignore else
|
|
51
78
|
if (this.verbose)
|
|
52
79
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
53
80
|
switch (msg.type) {
|
|
@@ -99,11 +126,13 @@ export class Frontend extends EventEmitter {
|
|
|
99
126
|
this.server.respond({ ...msg, result: { success: true } });
|
|
100
127
|
break;
|
|
101
128
|
default:
|
|
129
|
+
// istanbul ignore next
|
|
102
130
|
if (this.verbose)
|
|
103
131
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
134
|
if (this.server.isWorkerResponse(msg) && msg.result) {
|
|
135
|
+
// istanbul ignore next
|
|
107
136
|
if (this.verbose)
|
|
108
137
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
109
138
|
switch (msg.type) {
|
|
@@ -139,23 +168,55 @@ export class Frontend extends EventEmitter {
|
|
|
139
168
|
this.port = port;
|
|
140
169
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
141
170
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
171
|
+
// Initialize multer with the upload directory
|
|
142
172
|
const multer = await import('multer');
|
|
143
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
173
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
144
174
|
const upload = multer.default({ dest: uploadDir });
|
|
175
|
+
// Create the express app that serves the frontend
|
|
145
176
|
const express = await import('express');
|
|
146
177
|
this.expressApp = express.default();
|
|
178
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
179
|
+
/*
|
|
180
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
181
|
+
for (const method of methods) {
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
186
|
+
try {
|
|
187
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
188
|
+
return original(path, ...rest);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
*/
|
|
196
|
+
// Log all requests to the server for debugging
|
|
197
|
+
/*
|
|
198
|
+
this.expressApp.use((req, res, next) => {
|
|
199
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
200
|
+
next();
|
|
201
|
+
});
|
|
202
|
+
*/
|
|
203
|
+
// Serve static files from 'frontend/build' directory
|
|
147
204
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
|
|
205
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
148
206
|
this.log.debug(`Creating WebSocketServer...`);
|
|
149
207
|
const ws = await import('ws');
|
|
150
208
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
151
209
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
152
210
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
153
211
|
const clientIp = request.socket.remoteAddress;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (this.matterbridge.getLogLevel() === "
|
|
158
|
-
callbackLogLevel = "
|
|
212
|
+
// Set the global logger callback for the WebSocketServer
|
|
213
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
214
|
+
// istanbul ignore else
|
|
215
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
216
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
217
|
+
// istanbul ignore else
|
|
218
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
219
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
159
220
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
160
221
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
161
222
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -171,22 +232,33 @@ export class Frontend extends EventEmitter {
|
|
|
171
232
|
});
|
|
172
233
|
ws.on('close', () => {
|
|
173
234
|
this.log.info('WebSocket client disconnected');
|
|
235
|
+
// istanbul ignore else
|
|
174
236
|
if (this.webSocketServer?.clients.size === 0) {
|
|
175
237
|
AnsiLogger.setGlobalCallback(undefined);
|
|
176
238
|
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
177
239
|
}
|
|
178
240
|
});
|
|
241
|
+
// istanbul ignore next
|
|
179
242
|
ws.on('error', (error) => {
|
|
243
|
+
// istanbul ignore next
|
|
180
244
|
this.log.error(`WebSocket client error: ${error}`);
|
|
181
245
|
});
|
|
182
246
|
});
|
|
183
247
|
this.webSocketServer.on('close', () => {
|
|
184
248
|
this.log.debug(`WebSocketServer closed`);
|
|
185
249
|
});
|
|
250
|
+
/* With { noServer: true } it never fires
|
|
251
|
+
this.webSocketServer.on('listening', () => {
|
|
252
|
+
this.log.info(`The WebSocketServer is listening`);
|
|
253
|
+
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
254
|
+
});
|
|
255
|
+
*/
|
|
256
|
+
// istanbul ignore next
|
|
186
257
|
this.webSocketServer.on('error', (ws, error) => {
|
|
187
258
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
188
259
|
});
|
|
189
260
|
if (!hasParameter('ssl')) {
|
|
261
|
+
// Create an HTTP server and attach the express app
|
|
190
262
|
const http = await import('node:http');
|
|
191
263
|
try {
|
|
192
264
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -197,6 +269,7 @@ export class Frontend extends EventEmitter {
|
|
|
197
269
|
this.emit('server_error', error);
|
|
198
270
|
return;
|
|
199
271
|
}
|
|
272
|
+
// Listen on the specified port
|
|
200
273
|
if (hasParameter('ingress')) {
|
|
201
274
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
202
275
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -206,8 +279,10 @@ export class Frontend extends EventEmitter {
|
|
|
206
279
|
}
|
|
207
280
|
else {
|
|
208
281
|
this.httpServer.listen(this.port, () => {
|
|
282
|
+
// istanbul ignore else
|
|
209
283
|
if (this.matterbridge.systemInformation.ipv4Address !== '')
|
|
210
284
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
285
|
+
// istanbul ignore else
|
|
211
286
|
if (this.matterbridge.systemInformation.ipv6Address !== '')
|
|
212
287
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
213
288
|
this.listening = true;
|
|
@@ -216,24 +291,30 @@ export class Frontend extends EventEmitter {
|
|
|
216
291
|
}
|
|
217
292
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
218
293
|
try {
|
|
294
|
+
// Only proceed for real WebSocket upgrades
|
|
295
|
+
// istanbul ignore next cause is only a safety check
|
|
219
296
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
220
297
|
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
221
298
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
222
299
|
return socket.destroy();
|
|
223
300
|
}
|
|
301
|
+
// Build a URL so we can read ?password=...
|
|
224
302
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
303
|
+
// Validate WebSocket password
|
|
225
304
|
const password = url.searchParams.get('password') ?? '';
|
|
226
305
|
if (password !== this.storedPassword) {
|
|
227
306
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
228
307
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
229
308
|
return socket.destroy();
|
|
230
309
|
}
|
|
310
|
+
// Complete the WebSocket handshake
|
|
231
311
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
232
312
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
233
313
|
this.webSocketServer?.emit('connection', ws, req);
|
|
234
314
|
});
|
|
235
315
|
}
|
|
236
316
|
catch (err) {
|
|
317
|
+
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
237
318
|
{
|
|
238
319
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
239
320
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -256,6 +337,7 @@ export class Frontend extends EventEmitter {
|
|
|
256
337
|
});
|
|
257
338
|
}
|
|
258
339
|
else {
|
|
340
|
+
// SSL is enabled, load the certificate and the private key
|
|
259
341
|
let cert;
|
|
260
342
|
let key;
|
|
261
343
|
let ca;
|
|
@@ -265,6 +347,7 @@ export class Frontend extends EventEmitter {
|
|
|
265
347
|
let httpsServerOptions = {};
|
|
266
348
|
const fs = await import('node:fs');
|
|
267
349
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
350
|
+
// Load the p12 certificate and the passphrase
|
|
268
351
|
try {
|
|
269
352
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
270
353
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -276,7 +359,7 @@ export class Frontend extends EventEmitter {
|
|
|
276
359
|
}
|
|
277
360
|
try {
|
|
278
361
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
279
|
-
passphrase = passphrase.trim();
|
|
362
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
280
363
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
281
364
|
}
|
|
282
365
|
catch (error) {
|
|
@@ -287,6 +370,7 @@ export class Frontend extends EventEmitter {
|
|
|
287
370
|
httpsServerOptions = { pfx, passphrase };
|
|
288
371
|
}
|
|
289
372
|
else {
|
|
373
|
+
// 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.
|
|
290
374
|
try {
|
|
291
375
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
292
376
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -316,9 +400,10 @@ export class Frontend extends EventEmitter {
|
|
|
316
400
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
317
401
|
}
|
|
318
402
|
if (hasParameter('mtls')) {
|
|
319
|
-
httpsServerOptions.requestCert = true;
|
|
320
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
403
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
404
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
321
405
|
}
|
|
406
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
322
407
|
const https = await import('node:https');
|
|
323
408
|
try {
|
|
324
409
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -329,6 +414,7 @@ export class Frontend extends EventEmitter {
|
|
|
329
414
|
this.emit('server_error', error);
|
|
330
415
|
return;
|
|
331
416
|
}
|
|
417
|
+
// Listen on the specified port
|
|
332
418
|
if (hasParameter('ingress')) {
|
|
333
419
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
334
420
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -338,8 +424,10 @@ export class Frontend extends EventEmitter {
|
|
|
338
424
|
}
|
|
339
425
|
else {
|
|
340
426
|
this.httpsServer.listen(this.port, () => {
|
|
427
|
+
// istanbul ignore else
|
|
341
428
|
if (this.matterbridge.systemInformation.ipv4Address !== '')
|
|
342
429
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.matterbridge.systemInformation.ipv4Address}:${this.port}${UNDERLINEOFF}${rs}`);
|
|
430
|
+
// istanbul ignore else
|
|
343
431
|
if (this.matterbridge.systemInformation.ipv6Address !== '')
|
|
344
432
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
345
433
|
this.listening = true;
|
|
@@ -348,23 +436,29 @@ export class Frontend extends EventEmitter {
|
|
|
348
436
|
}
|
|
349
437
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
350
438
|
try {
|
|
439
|
+
// Only proceed for real WebSocket upgrades
|
|
440
|
+
// istanbul ignore next cause is only a safety check
|
|
351
441
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
352
442
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
353
443
|
return socket.destroy();
|
|
354
444
|
}
|
|
445
|
+
// Build a URL so we can read ?password=...
|
|
355
446
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
447
|
+
// Validate WebSocket password
|
|
356
448
|
const password = url.searchParams.get('password') ?? '';
|
|
357
449
|
if (password !== this.storedPassword) {
|
|
358
450
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
359
451
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
360
452
|
return socket.destroy();
|
|
361
453
|
}
|
|
454
|
+
// Complete the WebSocket handshake
|
|
362
455
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
363
456
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
364
457
|
this.webSocketServer?.emit('connection', ws, req);
|
|
365
458
|
});
|
|
366
459
|
}
|
|
367
460
|
catch (err) {
|
|
461
|
+
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
368
462
|
{
|
|
369
463
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
370
464
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -386,6 +480,7 @@ export class Frontend extends EventEmitter {
|
|
|
386
480
|
return;
|
|
387
481
|
});
|
|
388
482
|
}
|
|
483
|
+
// Subscribe to cli events
|
|
389
484
|
cliEmitter.removeAllListeners();
|
|
390
485
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
391
486
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -396,6 +491,8 @@ export class Frontend extends EventEmitter {
|
|
|
396
491
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
397
492
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
398
493
|
});
|
|
494
|
+
// Endpoint to validate login code
|
|
495
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
399
496
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
400
497
|
const { password } = req.body;
|
|
401
498
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -408,17 +505,20 @@ export class Frontend extends EventEmitter {
|
|
|
408
505
|
res.json({ valid: false });
|
|
409
506
|
}
|
|
410
507
|
});
|
|
508
|
+
// Endpoint to provide health check for docker
|
|
411
509
|
this.expressApp.get('/health', (req, res) => {
|
|
412
510
|
this.log.debug('Express received /health');
|
|
413
511
|
const healthStatus = {
|
|
414
|
-
status: 'ok',
|
|
415
|
-
uptime: process.uptime(),
|
|
416
|
-
timestamp: new Date().toISOString(),
|
|
512
|
+
status: 'ok', // Indicate service is healthy
|
|
513
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
514
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
417
515
|
};
|
|
418
516
|
res.status(200).json(healthStatus);
|
|
419
517
|
});
|
|
518
|
+
// Endpoint to provide memory usage details
|
|
420
519
|
this.expressApp.get('/memory', async (req, res) => {
|
|
421
520
|
this.log.debug('Express received /memory');
|
|
521
|
+
// Memory usage from process
|
|
422
522
|
const memoryUsageRaw = process.memoryUsage();
|
|
423
523
|
const memoryUsage = {
|
|
424
524
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -427,10 +527,13 @@ export class Frontend extends EventEmitter {
|
|
|
427
527
|
external: formatBytes(memoryUsageRaw.external),
|
|
428
528
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
429
529
|
};
|
|
530
|
+
// V8 heap statistics
|
|
430
531
|
const { default: v8 } = await import('node:v8');
|
|
431
532
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
432
533
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
534
|
+
// Format heapStats
|
|
433
535
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
536
|
+
// Format heapSpaces
|
|
434
537
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
435
538
|
...space,
|
|
436
539
|
space_size: formatBytes(space.space_size),
|
|
@@ -449,18 +552,22 @@ export class Frontend extends EventEmitter {
|
|
|
449
552
|
};
|
|
450
553
|
res.status(200).json(memoryReport);
|
|
451
554
|
});
|
|
555
|
+
// Endpoint to provide settings
|
|
452
556
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
453
557
|
this.log.debug('The frontend sent /api/settings');
|
|
454
558
|
res.json(await this.getApiSettings());
|
|
455
559
|
});
|
|
560
|
+
// Endpoint to provide plugins
|
|
456
561
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
457
562
|
this.log.debug('The frontend sent /api/plugins');
|
|
458
563
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
459
564
|
});
|
|
565
|
+
// Endpoint to provide devices
|
|
460
566
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
461
567
|
this.log.debug('The frontend sent /api/devices');
|
|
462
568
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
463
569
|
});
|
|
570
|
+
// Endpoint to view the matterbridge log
|
|
464
571
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
465
572
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
466
573
|
try {
|
|
@@ -474,6 +581,7 @@ export class Frontend extends EventEmitter {
|
|
|
474
581
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
475
582
|
}
|
|
476
583
|
});
|
|
584
|
+
// Endpoint to view the matter.js log
|
|
477
585
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
478
586
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
479
587
|
try {
|
|
@@ -487,6 +595,7 @@ export class Frontend extends EventEmitter {
|
|
|
487
595
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
488
596
|
}
|
|
489
597
|
});
|
|
598
|
+
// Endpoint to view the diagnostic.log
|
|
490
599
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
491
600
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
492
601
|
await this.generateDiagnostic();
|
|
@@ -497,10 +606,13 @@ export class Frontend extends EventEmitter {
|
|
|
497
606
|
res.send(data.slice(29));
|
|
498
607
|
}
|
|
499
608
|
catch (error) {
|
|
609
|
+
// istanbul ignore next
|
|
500
610
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
611
|
+
// istanbul ignore next
|
|
501
612
|
res.status(500).send('Error reading diagnostic log file.');
|
|
502
613
|
}
|
|
503
614
|
});
|
|
615
|
+
// Endpoint to download the diagnostic.log
|
|
504
616
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
505
617
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
506
618
|
await this.generateDiagnostic();
|
|
@@ -511,16 +623,19 @@ export class Frontend extends EventEmitter {
|
|
|
511
623
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
512
624
|
}
|
|
513
625
|
catch (error) {
|
|
626
|
+
// istanbul ignore next
|
|
514
627
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
515
628
|
}
|
|
516
629
|
res.type('text/plain');
|
|
517
630
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
631
|
+
/* istanbul ignore if */
|
|
518
632
|
if (error) {
|
|
519
633
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
520
634
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
521
635
|
}
|
|
522
636
|
});
|
|
523
637
|
});
|
|
638
|
+
// Endpoint to view the history.html
|
|
524
639
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
525
640
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
526
641
|
try {
|
|
@@ -534,6 +649,7 @@ export class Frontend extends EventEmitter {
|
|
|
534
649
|
res.status(500).send('Error reading history file.');
|
|
535
650
|
}
|
|
536
651
|
});
|
|
652
|
+
// Endpoint to download the history.html
|
|
537
653
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
538
654
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
539
655
|
try {
|
|
@@ -543,6 +659,7 @@ export class Frontend extends EventEmitter {
|
|
|
543
659
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
544
660
|
res.type('text/plain');
|
|
545
661
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
662
|
+
/* istanbul ignore if */
|
|
546
663
|
if (error) {
|
|
547
664
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
548
665
|
res.status(500).send('Error downloading history file');
|
|
@@ -554,6 +671,7 @@ export class Frontend extends EventEmitter {
|
|
|
554
671
|
res.status(500).send('Error reading history file.');
|
|
555
672
|
}
|
|
556
673
|
});
|
|
674
|
+
// Endpoint to view the shelly log
|
|
557
675
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
558
676
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
559
677
|
try {
|
|
@@ -567,6 +685,7 @@ export class Frontend extends EventEmitter {
|
|
|
567
685
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
568
686
|
}
|
|
569
687
|
});
|
|
688
|
+
// Endpoint to download the matterbridge log
|
|
570
689
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
571
690
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
572
691
|
const fs = await import('node:fs');
|
|
@@ -581,12 +700,14 @@ export class Frontend extends EventEmitter {
|
|
|
581
700
|
}
|
|
582
701
|
res.type('text/plain');
|
|
583
702
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
703
|
+
/* istanbul ignore if */
|
|
584
704
|
if (error) {
|
|
585
705
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
586
706
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
587
707
|
}
|
|
588
708
|
});
|
|
589
709
|
});
|
|
710
|
+
// Endpoint to download the matter log
|
|
590
711
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
591
712
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
592
713
|
const fs = await import('node:fs');
|
|
@@ -601,12 +722,14 @@ export class Frontend extends EventEmitter {
|
|
|
601
722
|
}
|
|
602
723
|
res.type('text/plain');
|
|
603
724
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
725
|
+
/* istanbul ignore if */
|
|
604
726
|
if (error) {
|
|
605
727
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
606
728
|
res.status(500).send('Error downloading the matter log file');
|
|
607
729
|
}
|
|
608
730
|
});
|
|
609
731
|
});
|
|
732
|
+
// Endpoint to download the shelly log
|
|
610
733
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
611
734
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
612
735
|
const fs = await import('node:fs');
|
|
@@ -621,75 +744,91 @@ export class Frontend extends EventEmitter {
|
|
|
621
744
|
}
|
|
622
745
|
res.type('text/plain');
|
|
623
746
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
747
|
+
/* istanbul ignore if */
|
|
624
748
|
if (error) {
|
|
625
749
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
626
750
|
res.status(500).send('Error downloading Shelly system log file');
|
|
627
751
|
}
|
|
628
752
|
});
|
|
629
753
|
});
|
|
754
|
+
// Endpoint to download the matterbridge storage directory
|
|
630
755
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
631
756
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
632
757
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
633
758
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
759
|
+
/* istanbul ignore if */
|
|
634
760
|
if (error) {
|
|
635
761
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
636
762
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
637
763
|
}
|
|
638
764
|
});
|
|
639
765
|
});
|
|
766
|
+
// Endpoint to download the matter storage file
|
|
640
767
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
641
768
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
642
769
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
643
770
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
771
|
+
/* istanbul ignore if */
|
|
644
772
|
if (error) {
|
|
645
773
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
646
774
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
647
775
|
}
|
|
648
776
|
});
|
|
649
777
|
});
|
|
778
|
+
// Endpoint to download the matterbridge plugin directory
|
|
650
779
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
651
780
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
652
781
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
653
782
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
783
|
+
/* istanbul ignore if */
|
|
654
784
|
if (error) {
|
|
655
785
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
656
786
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
657
787
|
}
|
|
658
788
|
});
|
|
659
789
|
});
|
|
790
|
+
// Endpoint to download the matterbridge plugin config files
|
|
660
791
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
661
792
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
662
793
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
663
794
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
795
|
+
/* istanbul ignore if */
|
|
664
796
|
if (error) {
|
|
665
797
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
666
798
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
667
799
|
}
|
|
668
800
|
});
|
|
669
801
|
});
|
|
802
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
670
803
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
671
804
|
this.log.debug('The frontend sent /api/download-backup');
|
|
672
805
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
806
|
+
/* istanbul ignore if */
|
|
673
807
|
if (error) {
|
|
674
808
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
675
809
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
676
810
|
}
|
|
677
811
|
});
|
|
678
812
|
});
|
|
813
|
+
// Endpoint to upload a package
|
|
679
814
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
680
815
|
const { filename } = req.body;
|
|
681
816
|
const file = req.file;
|
|
817
|
+
/* istanbul ignore if */
|
|
682
818
|
if (!file || !filename) {
|
|
683
819
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
684
820
|
res.status(400).send('Invalid request: file and filename are required');
|
|
685
821
|
return;
|
|
686
822
|
}
|
|
687
823
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
824
|
+
// Define the path where the plugin file will be saved
|
|
688
825
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
689
826
|
try {
|
|
827
|
+
// Move the uploaded file to the specified path
|
|
690
828
|
const fs = await import('node:fs');
|
|
691
829
|
await fs.promises.rename(file.path, filePath);
|
|
692
830
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
831
|
+
// Install the plugin package
|
|
693
832
|
if (filename.endsWith('.tgz')) {
|
|
694
833
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
695
834
|
if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
|
|
@@ -717,6 +856,7 @@ export class Frontend extends EventEmitter {
|
|
|
717
856
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
718
857
|
}
|
|
719
858
|
});
|
|
859
|
+
// Fallback for routing (must be the last route)
|
|
720
860
|
this.expressApp.use((req, res) => {
|
|
721
861
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
722
862
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -727,13 +867,16 @@ export class Frontend extends EventEmitter {
|
|
|
727
867
|
async stop() {
|
|
728
868
|
this.log.debug('Stopping the frontend...');
|
|
729
869
|
const ws = await import('ws');
|
|
870
|
+
// Remove listeners from the express app
|
|
730
871
|
if (this.expressApp) {
|
|
731
872
|
this.expressApp.removeAllListeners();
|
|
732
873
|
this.expressApp = undefined;
|
|
733
874
|
this.log.debug('Frontend app closed successfully');
|
|
734
875
|
}
|
|
876
|
+
// Close the WebSocket server
|
|
735
877
|
if (this.webSocketServer) {
|
|
736
878
|
this.log.debug('Closing WebSocket server...');
|
|
879
|
+
// Close all active connections
|
|
737
880
|
this.webSocketServer.clients.forEach((client) => {
|
|
738
881
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
739
882
|
client.close();
|
|
@@ -741,7 +884,9 @@ export class Frontend extends EventEmitter {
|
|
|
741
884
|
});
|
|
742
885
|
await withTimeout(new Promise((resolve) => {
|
|
743
886
|
this.webSocketServer?.close((error) => {
|
|
887
|
+
// istanbul ignore if
|
|
744
888
|
if (error) {
|
|
889
|
+
// istanbul ignore next
|
|
745
890
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
746
891
|
}
|
|
747
892
|
else {
|
|
@@ -754,8 +899,27 @@ export class Frontend extends EventEmitter {
|
|
|
754
899
|
this.webSocketServer.removeAllListeners();
|
|
755
900
|
this.webSocketServer = undefined;
|
|
756
901
|
}
|
|
902
|
+
// Close the http server
|
|
757
903
|
if (this.httpServer) {
|
|
758
904
|
this.log.debug('Closing http server...');
|
|
905
|
+
/*
|
|
906
|
+
await withTimeout(
|
|
907
|
+
new Promise<void>((resolve) => {
|
|
908
|
+
this.httpServer?.close((error) => {
|
|
909
|
+
if (error) {
|
|
910
|
+
// istanbul ignore next
|
|
911
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
912
|
+
} else {
|
|
913
|
+
this.log.debug('Http server closed successfully');
|
|
914
|
+
this.emit('server_stopped');
|
|
915
|
+
}
|
|
916
|
+
resolve();
|
|
917
|
+
});
|
|
918
|
+
}),
|
|
919
|
+
5000,
|
|
920
|
+
false,
|
|
921
|
+
);
|
|
922
|
+
*/
|
|
759
923
|
this.httpServer.close();
|
|
760
924
|
this.log.debug('Http server closed successfully');
|
|
761
925
|
this.listening = false;
|
|
@@ -764,8 +928,27 @@ export class Frontend extends EventEmitter {
|
|
|
764
928
|
this.httpServer = undefined;
|
|
765
929
|
this.log.debug('Frontend http server closed successfully');
|
|
766
930
|
}
|
|
931
|
+
// Close the https server
|
|
767
932
|
if (this.httpsServer) {
|
|
768
933
|
this.log.debug('Closing https server...');
|
|
934
|
+
/*
|
|
935
|
+
await withTimeout(
|
|
936
|
+
new Promise<void>((resolve) => {
|
|
937
|
+
this.httpsServer?.close((error) => {
|
|
938
|
+
if (error) {
|
|
939
|
+
// istanbul ignore next
|
|
940
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
941
|
+
} else {
|
|
942
|
+
this.log.debug('Https server closed successfully');
|
|
943
|
+
this.emit('server_stopped');
|
|
944
|
+
}
|
|
945
|
+
resolve();
|
|
946
|
+
});
|
|
947
|
+
}),
|
|
948
|
+
5000,
|
|
949
|
+
false,
|
|
950
|
+
);
|
|
951
|
+
*/
|
|
769
952
|
this.httpsServer.close();
|
|
770
953
|
this.log.debug('Https server closed successfully');
|
|
771
954
|
this.listening = false;
|
|
@@ -776,7 +959,13 @@ export class Frontend extends EventEmitter {
|
|
|
776
959
|
}
|
|
777
960
|
this.log.debug('Frontend stopped successfully');
|
|
778
961
|
}
|
|
962
|
+
/**
|
|
963
|
+
* Retrieves the api settings data.
|
|
964
|
+
*
|
|
965
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
966
|
+
*/
|
|
779
967
|
async getApiSettings() {
|
|
968
|
+
// Update the variable system information properties
|
|
780
969
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
781
970
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
782
971
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -786,6 +975,7 @@ export class Frontend extends EventEmitter {
|
|
|
786
975
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
787
976
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
788
977
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
978
|
+
// Create the matterbridge information
|
|
789
979
|
const info = {
|
|
790
980
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
791
981
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -821,9 +1011,15 @@ export class Frontend extends EventEmitter {
|
|
|
821
1011
|
};
|
|
822
1012
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
823
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Retrieves the reachable attribute.
|
|
1016
|
+
*
|
|
1017
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
1018
|
+
* @returns {boolean} The reachable attribute.
|
|
1019
|
+
*/
|
|
824
1020
|
getReachability(device) {
|
|
825
1021
|
if (this.matterbridge.hasCleanupStarted)
|
|
826
|
-
return false;
|
|
1022
|
+
return false; // Skip if cleanup has started
|
|
827
1023
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
828
1024
|
return false;
|
|
829
1025
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -834,9 +1030,15 @@ export class Frontend extends EventEmitter {
|
|
|
834
1030
|
return true;
|
|
835
1031
|
return false;
|
|
836
1032
|
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Retrieves the power source attribute.
|
|
1035
|
+
*
|
|
1036
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1037
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1038
|
+
*/
|
|
837
1039
|
getPowerSource(endpoint) {
|
|
838
1040
|
if (this.matterbridge.hasCleanupStarted)
|
|
839
|
-
return undefined;
|
|
1041
|
+
return undefined; // Skip if cleanup has started
|
|
840
1042
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
841
1043
|
return undefined;
|
|
842
1044
|
const powerSource = (device) => {
|
|
@@ -851,16 +1053,25 @@ export class Frontend extends EventEmitter {
|
|
|
851
1053
|
}
|
|
852
1054
|
return;
|
|
853
1055
|
};
|
|
1056
|
+
// Root endpoint
|
|
854
1057
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
855
1058
|
return powerSource(endpoint);
|
|
1059
|
+
// Child endpoints
|
|
856
1060
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1061
|
+
// istanbul ignore else
|
|
857
1062
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
858
1063
|
return powerSource(child);
|
|
859
1064
|
}
|
|
860
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Retrieves the battery level attribute.
|
|
1068
|
+
*
|
|
1069
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1070
|
+
* @returns {number | undefined} The battery level attribute.
|
|
1071
|
+
*/
|
|
861
1072
|
getBatteryLevel(endpoint) {
|
|
862
1073
|
if (this.matterbridge.hasCleanupStarted)
|
|
863
|
-
return undefined;
|
|
1074
|
+
return undefined; // Skip if cleanup has started
|
|
864
1075
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
865
1076
|
return undefined;
|
|
866
1077
|
const batteryLevel = (device) => {
|
|
@@ -871,16 +1082,27 @@ export class Frontend extends EventEmitter {
|
|
|
871
1082
|
}
|
|
872
1083
|
return undefined;
|
|
873
1084
|
};
|
|
1085
|
+
// Root endpoint
|
|
874
1086
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
875
1087
|
return batteryLevel(endpoint);
|
|
1088
|
+
// Child endpoints
|
|
876
1089
|
for (const child of endpoint.getChildEndpoints()) {
|
|
1090
|
+
// istanbul ignore else
|
|
877
1091
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
878
1092
|
return batteryLevel(child);
|
|
879
1093
|
}
|
|
880
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Retrieves the cluster text description from a given device.
|
|
1097
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1098
|
+
*
|
|
1099
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1100
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1101
|
+
*/
|
|
881
1102
|
getClusterTextFromDevice(device) {
|
|
882
1103
|
if (this.matterbridge.hasCleanupStarted)
|
|
883
|
-
return '';
|
|
1104
|
+
return ''; // Skip if cleanup has started
|
|
1105
|
+
// istanbul ignore else
|
|
884
1106
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
885
1107
|
return '';
|
|
886
1108
|
const getUserLabel = (device) => {
|
|
@@ -890,6 +1112,7 @@ export class Frontend extends EventEmitter {
|
|
|
890
1112
|
if (composed)
|
|
891
1113
|
return 'Composed: ' + composed.value;
|
|
892
1114
|
}
|
|
1115
|
+
// istanbul ignore next cause is not reachable
|
|
893
1116
|
return '';
|
|
894
1117
|
};
|
|
895
1118
|
const getFixedLabel = (device) => {
|
|
@@ -899,11 +1122,13 @@ export class Frontend extends EventEmitter {
|
|
|
899
1122
|
if (composed)
|
|
900
1123
|
return 'Composed: ' + composed.value;
|
|
901
1124
|
}
|
|
1125
|
+
// istanbul ignore next cause is not reacheable
|
|
902
1126
|
return '';
|
|
903
1127
|
};
|
|
904
1128
|
let attributes = '';
|
|
905
1129
|
let supportedModes = [];
|
|
906
1130
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1131
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
907
1132
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
908
1133
|
return;
|
|
909
1134
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -993,11 +1218,17 @@ export class Frontend extends EventEmitter {
|
|
|
993
1218
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
994
1219
|
attributes += `${getUserLabel(device)} `;
|
|
995
1220
|
});
|
|
1221
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
996
1222
|
return attributes.trimStart().trimEnd();
|
|
997
1223
|
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1226
|
+
*
|
|
1227
|
+
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1228
|
+
*/
|
|
998
1229
|
getPlugins() {
|
|
999
1230
|
if (this.matterbridge.hasCleanupStarted)
|
|
1000
|
-
return [];
|
|
1231
|
+
return []; // Skip if cleanup has started
|
|
1001
1232
|
const plugins = [];
|
|
1002
1233
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
1003
1234
|
plugins.push({
|
|
@@ -1025,18 +1256,27 @@ export class Frontend extends EventEmitter {
|
|
|
1025
1256
|
schemaJson: plugin.schemaJson,
|
|
1026
1257
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1027
1258
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1259
|
+
// Childbridge mode specific data
|
|
1028
1260
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1029
1261
|
});
|
|
1030
1262
|
}
|
|
1031
1263
|
return plugins;
|
|
1032
1264
|
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Retrieves the devices from Matterbridge.
|
|
1267
|
+
*
|
|
1268
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1269
|
+
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1270
|
+
*/
|
|
1033
1271
|
getDevices(pluginName) {
|
|
1034
1272
|
if (this.matterbridge.hasCleanupStarted)
|
|
1035
|
-
return [];
|
|
1273
|
+
return []; // Skip if cleanup has started
|
|
1036
1274
|
const devices = [];
|
|
1037
1275
|
for (const device of this.matterbridge.devices.array()) {
|
|
1276
|
+
// Filter by pluginName if provided
|
|
1038
1277
|
if (pluginName && pluginName !== device.plugin)
|
|
1039
1278
|
continue;
|
|
1279
|
+
// Check if the device has the required properties
|
|
1040
1280
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1041
1281
|
continue;
|
|
1042
1282
|
devices.push({
|
|
@@ -1057,24 +1297,39 @@ export class Frontend extends EventEmitter {
|
|
|
1057
1297
|
}
|
|
1058
1298
|
return devices;
|
|
1059
1299
|
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1302
|
+
*
|
|
1303
|
+
* Response for /api/clusters
|
|
1304
|
+
*
|
|
1305
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1306
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1307
|
+
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1308
|
+
*/
|
|
1060
1309
|
getClusters(pluginName, endpointNumber) {
|
|
1061
1310
|
if (this.matterbridge.hasCleanupStarted)
|
|
1062
|
-
return;
|
|
1311
|
+
return; // Skip if cleanup has started
|
|
1063
1312
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1064
1313
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1065
1314
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1066
1315
|
return;
|
|
1067
1316
|
}
|
|
1317
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1318
|
+
// Get the device types from the main endpoint
|
|
1068
1319
|
const deviceTypes = [];
|
|
1069
1320
|
const clusters = [];
|
|
1070
1321
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1071
1322
|
deviceTypes.push(d.deviceType);
|
|
1072
1323
|
});
|
|
1324
|
+
// Get the clusters from the main endpoint
|
|
1073
1325
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1074
1326
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1075
1327
|
return;
|
|
1076
1328
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1077
1329
|
return;
|
|
1330
|
+
// console.log(
|
|
1331
|
+
// `${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}`,
|
|
1332
|
+
// );
|
|
1078
1333
|
clusters.push({
|
|
1079
1334
|
endpoint: endpoint.number.toString(),
|
|
1080
1335
|
number: endpoint.number,
|
|
@@ -1088,12 +1343,19 @@ export class Frontend extends EventEmitter {
|
|
|
1088
1343
|
attributeLocalValue: attributeValue,
|
|
1089
1344
|
});
|
|
1090
1345
|
});
|
|
1346
|
+
// Get the child endpoints
|
|
1091
1347
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1348
|
+
// if (childEndpoints.length === 0) {
|
|
1349
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1350
|
+
// }
|
|
1092
1351
|
childEndpoints.forEach((childEndpoint) => {
|
|
1352
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1093
1353
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1094
1354
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1095
1355
|
return;
|
|
1096
1356
|
}
|
|
1357
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1358
|
+
// Get the device types of the child endpoint
|
|
1097
1359
|
const deviceTypes = [];
|
|
1098
1360
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1099
1361
|
deviceTypes.push(d.deviceType);
|
|
@@ -1103,6 +1365,9 @@ export class Frontend extends EventEmitter {
|
|
|
1103
1365
|
return;
|
|
1104
1366
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1105
1367
|
return;
|
|
1368
|
+
// console.log(
|
|
1369
|
+
// `${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}`,
|
|
1370
|
+
// );
|
|
1106
1371
|
clusters.push({
|
|
1107
1372
|
endpoint: childEndpoint.number.toString(),
|
|
1108
1373
|
number: childEndpoint.number,
|
|
@@ -1122,6 +1387,7 @@ export class Frontend extends EventEmitter {
|
|
|
1122
1387
|
async generateDiagnostic() {
|
|
1123
1388
|
this.log.debug('Generating diagnostic...');
|
|
1124
1389
|
const serverNodes = [];
|
|
1390
|
+
// istanbul ignore else
|
|
1125
1391
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1126
1392
|
if (this.matterbridge.serverNode)
|
|
1127
1393
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1132,6 +1398,7 @@ export class Frontend extends EventEmitter {
|
|
|
1132
1398
|
serverNodes.push(plugin.serverNode);
|
|
1133
1399
|
}
|
|
1134
1400
|
}
|
|
1401
|
+
// istanbul ignore next
|
|
1135
1402
|
for (const device of this.matterbridge.devices.array()) {
|
|
1136
1403
|
if (device.serverNode)
|
|
1137
1404
|
serverNodes.push(device.serverNode);
|
|
@@ -1155,8 +1422,15 @@ export class Frontend extends EventEmitter {
|
|
|
1155
1422
|
values: [...serverNodes],
|
|
1156
1423
|
})));
|
|
1157
1424
|
delete Logger.destinations.diagnostic;
|
|
1158
|
-
await wait(500);
|
|
1425
|
+
await wait(500); // Wait for the log to be written
|
|
1159
1426
|
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1429
|
+
*
|
|
1430
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1431
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1432
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1433
|
+
*/
|
|
1160
1434
|
async wsMessageHandler(client, message) {
|
|
1161
1435
|
let data;
|
|
1162
1436
|
const sendResponse = (data) => {
|
|
@@ -1174,12 +1448,13 @@ export class Frontend extends EventEmitter {
|
|
|
1174
1448
|
client.send(JSON.stringify(data));
|
|
1175
1449
|
}
|
|
1176
1450
|
else {
|
|
1451
|
+
// istanbul ignore next cause is only a safety check
|
|
1177
1452
|
this.log.error('Cannot send api response, client not connected');
|
|
1178
1453
|
}
|
|
1179
1454
|
};
|
|
1180
1455
|
try {
|
|
1181
1456
|
data = JSON.parse(message.toString());
|
|
1182
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1457
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
1183
1458
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1184
1459
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1185
1460
|
return;
|
|
@@ -1236,7 +1511,22 @@ export class Frontend extends EventEmitter {
|
|
|
1236
1511
|
}
|
|
1237
1512
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1238
1513
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1239
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1514
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
|
|
1515
|
+
/*
|
|
1516
|
+
const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
|
|
1517
|
+
if (plugin) {
|
|
1518
|
+
this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
|
|
1519
|
+
await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
|
|
1520
|
+
this.wssSendRestartRequired();
|
|
1521
|
+
this.wssSendRefreshRequired('plugins');
|
|
1522
|
+
this.wssSendRefreshRequired('devices');
|
|
1523
|
+
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1524
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1525
|
+
} else {
|
|
1526
|
+
this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
|
|
1527
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
1528
|
+
}
|
|
1529
|
+
*/
|
|
1240
1530
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1241
1531
|
if (plugin) {
|
|
1242
1532
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1249,7 +1539,7 @@ export class Frontend extends EventEmitter {
|
|
|
1249
1539
|
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1250
1540
|
return;
|
|
1251
1541
|
})
|
|
1252
|
-
.catch((_error) => { });
|
|
1542
|
+
.catch(/* istanbul ignore next */ (_error) => { });
|
|
1253
1543
|
}
|
|
1254
1544
|
else {
|
|
1255
1545
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
@@ -1263,6 +1553,10 @@ export class Frontend extends EventEmitter {
|
|
|
1263
1553
|
}
|
|
1264
1554
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1265
1555
|
this.log.debug(`Removing plugin ${data.params.pluginName}...`);
|
|
1556
|
+
/*
|
|
1557
|
+
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);
|
|
1558
|
+
await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
|
|
1559
|
+
*/
|
|
1266
1560
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1267
1561
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1268
1562
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1297,7 +1591,7 @@ export class Frontend extends EventEmitter {
|
|
|
1297
1591
|
this.wssSendSnackbarMessage(`Started plugin ${localData.params.pluginName}`, 5, 'success');
|
|
1298
1592
|
return;
|
|
1299
1593
|
})
|
|
1300
|
-
.catch((_error) => { });
|
|
1594
|
+
.catch(/* istanbul ignore next */ (_error) => { });
|
|
1301
1595
|
}
|
|
1302
1596
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1303
1597
|
}
|
|
@@ -1322,6 +1616,7 @@ export class Frontend extends EventEmitter {
|
|
|
1322
1616
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1323
1617
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1324
1618
|
if (plugin.serverNode) {
|
|
1619
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1325
1620
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1326
1621
|
plugin.serverNode = undefined;
|
|
1327
1622
|
}
|
|
@@ -1331,18 +1626,20 @@ export class Frontend extends EventEmitter {
|
|
|
1331
1626
|
this.matterbridge.devices.remove(device);
|
|
1332
1627
|
}
|
|
1333
1628
|
}
|
|
1629
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1334
1630
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1335
1631
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1336
1632
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1337
|
-
plugin.restartRequired = false;
|
|
1633
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1338
1634
|
let needRestart = 0;
|
|
1339
1635
|
for (const plugin of this.matterbridge.plugins) {
|
|
1340
1636
|
if (plugin.restartRequired)
|
|
1341
1637
|
needRestart++;
|
|
1342
1638
|
}
|
|
1343
1639
|
if (needRestart === 0) {
|
|
1344
|
-
this.wssSendRestartNotRequired(true);
|
|
1640
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1345
1641
|
}
|
|
1642
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1346
1643
|
if (plugin.serverNode)
|
|
1347
1644
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1348
1645
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1487,6 +1784,9 @@ export class Frontend extends EventEmitter {
|
|
|
1487
1784
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1488
1785
|
}
|
|
1489
1786
|
if (data.params.advertise) {
|
|
1787
|
+
// TODO: matter.js 0.16.0
|
|
1788
|
+
// await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
|
|
1789
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1490
1790
|
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1491
1791
|
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1492
1792
|
await advertiser.advertise(true);
|
|
@@ -1548,6 +1848,7 @@ export class Frontend extends EventEmitter {
|
|
|
1548
1848
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
|
|
1549
1849
|
return;
|
|
1550
1850
|
}
|
|
1851
|
+
// istanbul ignore next
|
|
1551
1852
|
const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1552
1853
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
|
|
1553
1854
|
}
|
|
@@ -1561,6 +1862,7 @@ export class Frontend extends EventEmitter {
|
|
|
1561
1862
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
|
|
1562
1863
|
return;
|
|
1563
1864
|
}
|
|
1865
|
+
// istanbul ignore next
|
|
1564
1866
|
const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().sort((keyA, keyB) => keyA.name.localeCompare(keyB.name));
|
|
1565
1867
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
|
|
1566
1868
|
}
|
|
@@ -1612,22 +1914,22 @@ export class Frontend extends EventEmitter {
|
|
|
1612
1914
|
if (isValidString(data.params.value, 4)) {
|
|
1613
1915
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1614
1916
|
if (data.params.value === 'Debug') {
|
|
1615
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1917
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1616
1918
|
}
|
|
1617
1919
|
else if (data.params.value === 'Info') {
|
|
1618
|
-
await this.matterbridge.setLogLevel("info");
|
|
1920
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1619
1921
|
}
|
|
1620
1922
|
else if (data.params.value === 'Notice') {
|
|
1621
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1923
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1622
1924
|
}
|
|
1623
1925
|
else if (data.params.value === 'Warn') {
|
|
1624
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1926
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1625
1927
|
}
|
|
1626
1928
|
else if (data.params.value === 'Error') {
|
|
1627
|
-
await this.matterbridge.setLogLevel("error");
|
|
1929
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1628
1930
|
}
|
|
1629
1931
|
else if (data.params.value === 'Fatal') {
|
|
1630
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1932
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1631
1933
|
}
|
|
1632
1934
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1633
1935
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1638,6 +1940,7 @@ export class Frontend extends EventEmitter {
|
|
|
1638
1940
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1639
1941
|
this.matterbridge.fileLogger = data.params.value;
|
|
1640
1942
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1943
|
+
// Create the file logger for matterbridge
|
|
1641
1944
|
if (data.params.value)
|
|
1642
1945
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1643
1946
|
else
|
|
@@ -1667,11 +1970,12 @@ export class Frontend extends EventEmitter {
|
|
|
1667
1970
|
Logger.level = MatterLogLevel.FATAL;
|
|
1668
1971
|
}
|
|
1669
1972
|
this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1973
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1974
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1975
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1976
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1977
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1978
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1675
1979
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1676
1980
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1677
1981
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1723,6 +2027,7 @@ export class Frontend extends EventEmitter {
|
|
|
1723
2027
|
}
|
|
1724
2028
|
break;
|
|
1725
2029
|
case 'setmatterport':
|
|
2030
|
+
// eslint-disable-next-line no-case-declarations
|
|
1726
2031
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1727
2032
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1728
2033
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1742,6 +2047,7 @@ export class Frontend extends EventEmitter {
|
|
|
1742
2047
|
}
|
|
1743
2048
|
break;
|
|
1744
2049
|
case 'setmatterdiscriminator':
|
|
2050
|
+
// eslint-disable-next-line no-case-declarations
|
|
1745
2051
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1746
2052
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1747
2053
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1761,6 +2067,7 @@ export class Frontend extends EventEmitter {
|
|
|
1761
2067
|
}
|
|
1762
2068
|
break;
|
|
1763
2069
|
case 'setmatterpasscode':
|
|
2070
|
+
// eslint-disable-next-line no-case-declarations
|
|
1764
2071
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1765
2072
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1766
2073
|
this.matterbridge.passcode = passcode;
|
|
@@ -1806,15 +2113,19 @@ export class Frontend extends EventEmitter {
|
|
|
1806
2113
|
return;
|
|
1807
2114
|
}
|
|
1808
2115
|
const config = plugin.configJson;
|
|
2116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1809
2117
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2118
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1810
2119
|
if (select === 'serial')
|
|
1811
2120
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1812
2121
|
if (select === 'name')
|
|
1813
2122
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1814
2123
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2124
|
+
// Remove postfix from the serial if it exists
|
|
1815
2125
|
if (config.postfix) {
|
|
1816
2126
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1817
2127
|
}
|
|
2128
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1818
2129
|
if (isValidArray(config.whiteList, 1)) {
|
|
1819
2130
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1820
2131
|
config.whiteList.push(data.params.serial);
|
|
@@ -1823,6 +2134,7 @@ export class Frontend extends EventEmitter {
|
|
|
1823
2134
|
config.whiteList.push(data.params.name);
|
|
1824
2135
|
}
|
|
1825
2136
|
}
|
|
2137
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1826
2138
|
if (isValidArray(config.blackList, 1)) {
|
|
1827
2139
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1828
2140
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1850,7 +2162,9 @@ export class Frontend extends EventEmitter {
|
|
|
1850
2162
|
return;
|
|
1851
2163
|
}
|
|
1852
2164
|
const config = plugin.configJson;
|
|
2165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1853
2166
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2167
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1854
2168
|
if (select === 'serial')
|
|
1855
2169
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1856
2170
|
if (select === 'name')
|
|
@@ -1859,6 +2173,7 @@ export class Frontend extends EventEmitter {
|
|
|
1859
2173
|
if (config.postfix) {
|
|
1860
2174
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1861
2175
|
}
|
|
2176
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1862
2177
|
if (isValidArray(config.whiteList, 1)) {
|
|
1863
2178
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1864
2179
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1867,6 +2182,7 @@ export class Frontend extends EventEmitter {
|
|
|
1867
2182
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1868
2183
|
}
|
|
1869
2184
|
}
|
|
2185
|
+
// Add the serial to the blackList
|
|
1870
2186
|
if (isValidArray(config.blackList)) {
|
|
1871
2187
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1872
2188
|
config.blackList.push(data.params.serial);
|
|
@@ -1889,6 +2205,7 @@ export class Frontend extends EventEmitter {
|
|
|
1889
2205
|
}
|
|
1890
2206
|
}
|
|
1891
2207
|
else {
|
|
2208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1892
2209
|
const localData = data;
|
|
1893
2210
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1894
2211
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1898,23 +2215,46 @@ export class Frontend extends EventEmitter {
|
|
|
1898
2215
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1899
2216
|
}
|
|
1900
2217
|
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2220
|
+
*
|
|
2221
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2222
|
+
* @param {string} time - The time string of the message
|
|
2223
|
+
* @param {string} name - The logger name of the message
|
|
2224
|
+
* @param {string} message - The content of the message.
|
|
2225
|
+
*
|
|
2226
|
+
* @remarks
|
|
2227
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2228
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2229
|
+
* The function sends the message to all connected clients.
|
|
2230
|
+
*/
|
|
1901
2231
|
wssSendLogMessage(level, time, name, message) {
|
|
1902
2232
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1903
2233
|
return;
|
|
1904
2234
|
if (!level || !time || !name || !message)
|
|
1905
2235
|
return;
|
|
2236
|
+
// Remove ANSI escape codes from the message
|
|
2237
|
+
// eslint-disable-next-line no-control-regex
|
|
1906
2238
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2239
|
+
// Remove leading asterisks from the message
|
|
1907
2240
|
message = message.replace(/^\*+/, '');
|
|
2241
|
+
// Replace all occurrences of \t and \n
|
|
1908
2242
|
message = message.replace(/[\t\n]/g, '');
|
|
2243
|
+
// Remove non-printable characters
|
|
2244
|
+
// eslint-disable-next-line no-control-regex
|
|
1909
2245
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2246
|
+
// Replace all occurrences of \" with "
|
|
1910
2247
|
message = message.replace(/\\"/g, '"');
|
|
2248
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1911
2249
|
const maxContinuousLength = 100;
|
|
1912
2250
|
const keepStartLength = 20;
|
|
1913
2251
|
const keepEndLength = 20;
|
|
2252
|
+
// Split the message into words
|
|
1914
2253
|
if (level !== 'spawn') {
|
|
1915
2254
|
message = message
|
|
1916
2255
|
.split(' ')
|
|
1917
2256
|
.map((word) => {
|
|
2257
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1918
2258
|
if (word.length > maxContinuousLength) {
|
|
1919
2259
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1920
2260
|
}
|
|
@@ -1922,14 +2262,34 @@ export class Frontend extends EventEmitter {
|
|
|
1922
2262
|
})
|
|
1923
2263
|
.join(' ');
|
|
1924
2264
|
}
|
|
2265
|
+
// Send the message to all connected clients
|
|
1925
2266
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1926
2267
|
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2270
|
+
*
|
|
2271
|
+
* @param {string} changed - The changed value.
|
|
2272
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2273
|
+
* possible values for changed:
|
|
2274
|
+
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2275
|
+
* - 'plugins'
|
|
2276
|
+
* - 'devices'
|
|
2277
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2278
|
+
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2279
|
+
*/
|
|
1927
2280
|
wssSendRefreshRequired(changed, params) {
|
|
1928
2281
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1929
2282
|
return;
|
|
1930
2283
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2284
|
+
// Send the message to all connected clients
|
|
1931
2285
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1932
2286
|
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2289
|
+
*
|
|
2290
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2291
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2292
|
+
*/
|
|
1933
2293
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1934
2294
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1935
2295
|
return;
|
|
@@ -1938,8 +2298,14 @@ export class Frontend extends EventEmitter {
|
|
|
1938
2298
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
1939
2299
|
if (snackbar === true)
|
|
1940
2300
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2301
|
+
// Send the message to all connected clients
|
|
1941
2302
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1942
2303
|
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2306
|
+
*
|
|
2307
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2308
|
+
*/
|
|
1943
2309
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1944
2310
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1945
2311
|
return;
|
|
@@ -1947,64 +2313,145 @@ export class Frontend extends EventEmitter {
|
|
|
1947
2313
|
this.matterbridge.restartRequired = false;
|
|
1948
2314
|
if (snackbar === true)
|
|
1949
2315
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2316
|
+
// Send the message to all connected clients
|
|
1950
2317
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1951
2318
|
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2321
|
+
*
|
|
2322
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2323
|
+
*/
|
|
1952
2324
|
wssSendUpdateRequired(devVersion = false) {
|
|
1953
2325
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1954
2326
|
return;
|
|
1955
2327
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1956
2328
|
this.matterbridge.updateRequired = true;
|
|
2329
|
+
// Send the message to all connected clients
|
|
1957
2330
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1958
2331
|
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Sends a cpu update message to all connected clients.
|
|
2334
|
+
*
|
|
2335
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2336
|
+
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2337
|
+
*/
|
|
1959
2338
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
1960
2339
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1961
2340
|
return;
|
|
2341
|
+
// istanbul ignore else
|
|
1962
2342
|
if (hasParameter('debug'))
|
|
1963
2343
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2344
|
+
// Send the message to all connected clients
|
|
1964
2345
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100, processCpuUsage: Math.round(processCpuUsage * 100) / 100 } });
|
|
1965
2346
|
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Sends a memory update message to all connected clients.
|
|
2349
|
+
*
|
|
2350
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2351
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2352
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2353
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2354
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2355
|
+
* @param {string} external - The external memory in bytes.
|
|
2356
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2357
|
+
*/
|
|
1966
2358
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1967
2359
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1968
2360
|
return;
|
|
2361
|
+
// istanbul ignore else
|
|
1969
2362
|
if (hasParameter('debug'))
|
|
1970
2363
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2364
|
+
// Send the message to all connected clients
|
|
1971
2365
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1972
2366
|
}
|
|
2367
|
+
/**
|
|
2368
|
+
* Sends an uptime update message to all connected clients.
|
|
2369
|
+
*
|
|
2370
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2371
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2372
|
+
*/
|
|
1973
2373
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1974
2374
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1975
2375
|
return;
|
|
2376
|
+
// istanbul ignore else
|
|
1976
2377
|
if (hasParameter('debug'))
|
|
1977
2378
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2379
|
+
// Send the message to all connected clients
|
|
1978
2380
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1979
2381
|
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Sends an open snackbar message to all connected clients.
|
|
2384
|
+
*
|
|
2385
|
+
* @param {string} message - The message to send.
|
|
2386
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2387
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2388
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2389
|
+
*
|
|
2390
|
+
* @remarks
|
|
2391
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2392
|
+
*/
|
|
1980
2393
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1981
2394
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1982
2395
|
return;
|
|
1983
2396
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2397
|
+
// Send the message to all connected clients
|
|
1984
2398
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1985
2399
|
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Sends a close snackbar message to all connected clients.
|
|
2402
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2403
|
+
*
|
|
2404
|
+
* @param {string} message - The message to send.
|
|
2405
|
+
*/
|
|
1986
2406
|
wssSendCloseSnackbarMessage(message) {
|
|
1987
2407
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1988
2408
|
return;
|
|
1989
2409
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2410
|
+
// Send the message to all connected clients
|
|
1990
2411
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1991
2412
|
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2415
|
+
*
|
|
2416
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2417
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2418
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2419
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2420
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2421
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2422
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2423
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2424
|
+
*
|
|
2425
|
+
* @remarks
|
|
2426
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2427
|
+
* with the updated attribute information.
|
|
2428
|
+
*/
|
|
1992
2429
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1993
2430
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1994
2431
|
return;
|
|
1995
2432
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2433
|
+
// Send the message to all connected clients
|
|
1996
2434
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1997
2435
|
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Sends a message to all connected clients.
|
|
2438
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2439
|
+
*
|
|
2440
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2441
|
+
*/
|
|
1998
2442
|
wssBroadcastMessage(msg) {
|
|
1999
2443
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
2000
2444
|
return;
|
|
2445
|
+
// Send the message to all connected clients
|
|
2001
2446
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2002
2447
|
if (msg.method !== 'log')
|
|
2003
2448
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
2004
2449
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2450
|
+
// istanbul ignore else
|
|
2005
2451
|
if (client.readyState === client.OPEN) {
|
|
2006
2452
|
client.send(stringifiedMsg);
|
|
2007
2453
|
}
|
|
2008
2454
|
});
|
|
2009
2455
|
}
|
|
2010
2456
|
}
|
|
2457
|
+
//# sourceMappingURL=frontend.js.map
|