matterbridge 3.4.3-dev-20251209-e6cb85f → 3.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/broadcastServer.d.ts +144 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +119 -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 +238 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +455 -35
- 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 +492 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +811 -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 +1444 -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 +251 -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/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/scripts/data_model.mjs +2058 -0
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.2
|
|
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
|
|
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));
|
|
@@ -139,23 +165,53 @@ export class Frontend extends EventEmitter {
|
|
|
139
165
|
this.port = port;
|
|
140
166
|
this.storedPassword = await this.matterbridge.nodeContext?.get('password', '');
|
|
141
167
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
168
|
+
// Initialize multer with the upload directory
|
|
142
169
|
const multer = await import('multer');
|
|
143
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
170
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
144
171
|
const upload = multer.default({ dest: uploadDir });
|
|
172
|
+
// Create the express app that serves the frontend
|
|
145
173
|
const express = await import('express');
|
|
146
174
|
this.expressApp = express.default();
|
|
175
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
176
|
+
/*
|
|
177
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
178
|
+
for (const method of methods) {
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
183
|
+
try {
|
|
184
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
185
|
+
return original(path, ...rest);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
*/
|
|
193
|
+
// Log all requests to the server for debugging
|
|
194
|
+
/*
|
|
195
|
+
this.expressApp.use((req, res, next) => {
|
|
196
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
197
|
+
next();
|
|
198
|
+
});
|
|
199
|
+
*/
|
|
200
|
+
// Serve static files from 'frontend/build' directory
|
|
147
201
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend', 'build')));
|
|
202
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
148
203
|
this.log.debug(`Creating WebSocketServer...`);
|
|
149
204
|
const ws = await import('ws');
|
|
150
205
|
this.webSocketServer = new ws.WebSocketServer({ noServer: true });
|
|
151
206
|
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
152
207
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
153
208
|
const clientIp = request.socket.remoteAddress;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
209
|
+
// Set the global logger callback for the WebSocketServer
|
|
210
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
211
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
212
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
213
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
214
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
159
215
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
160
216
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
161
217
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -177,16 +233,25 @@ export class Frontend extends EventEmitter {
|
|
|
177
233
|
}
|
|
178
234
|
});
|
|
179
235
|
ws.on('error', (error) => {
|
|
236
|
+
// istanbul ignore next
|
|
180
237
|
this.log.error(`WebSocket client error: ${error}`);
|
|
181
238
|
});
|
|
182
239
|
});
|
|
183
240
|
this.webSocketServer.on('close', () => {
|
|
184
241
|
this.log.debug(`WebSocketServer closed`);
|
|
185
242
|
});
|
|
243
|
+
/* With { noServer: true } it never fires
|
|
244
|
+
this.webSocketServer.on('listening', () => {
|
|
245
|
+
this.log.info(`The WebSocketServer is listening`);
|
|
246
|
+
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
247
|
+
});
|
|
248
|
+
*/
|
|
249
|
+
// istanbul ignore next
|
|
186
250
|
this.webSocketServer.on('error', (ws, error) => {
|
|
187
251
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
188
252
|
});
|
|
189
253
|
if (!hasParameter('ssl')) {
|
|
254
|
+
// Create an HTTP server and attach the express app
|
|
190
255
|
const http = await import('node:http');
|
|
191
256
|
try {
|
|
192
257
|
this.log.debug(`Creating HTTP server...`);
|
|
@@ -197,6 +262,7 @@ export class Frontend extends EventEmitter {
|
|
|
197
262
|
this.emit('server_error', error);
|
|
198
263
|
return;
|
|
199
264
|
}
|
|
265
|
+
// Listen on the specified port
|
|
200
266
|
if (hasParameter('ingress')) {
|
|
201
267
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
202
268
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -216,24 +282,30 @@ export class Frontend extends EventEmitter {
|
|
|
216
282
|
}
|
|
217
283
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
218
284
|
try {
|
|
285
|
+
// Only proceed for real WebSocket upgrades
|
|
286
|
+
// istanbul ignore next cause is only a safety check
|
|
219
287
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
220
288
|
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
221
289
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
222
290
|
return socket.destroy();
|
|
223
291
|
}
|
|
292
|
+
// Build a URL so we can read ?password=...
|
|
224
293
|
const url = new URL(req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
|
|
294
|
+
// Validate WebSocket password
|
|
225
295
|
const password = url.searchParams.get('password') ?? '';
|
|
226
296
|
if (password !== this.storedPassword) {
|
|
227
297
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
228
298
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
229
299
|
return socket.destroy();
|
|
230
300
|
}
|
|
301
|
+
// Complete the WebSocket handshake
|
|
231
302
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
232
303
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
233
304
|
this.webSocketServer?.emit('connection', ws, req);
|
|
234
305
|
});
|
|
235
306
|
}
|
|
236
307
|
catch (err) {
|
|
308
|
+
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
237
309
|
{
|
|
238
310
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
239
311
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -256,6 +328,7 @@ export class Frontend extends EventEmitter {
|
|
|
256
328
|
});
|
|
257
329
|
}
|
|
258
330
|
else {
|
|
331
|
+
// SSL is enabled, load the certificate and the private key
|
|
259
332
|
let cert;
|
|
260
333
|
let key;
|
|
261
334
|
let ca;
|
|
@@ -265,6 +338,7 @@ export class Frontend extends EventEmitter {
|
|
|
265
338
|
let httpsServerOptions = {};
|
|
266
339
|
const fs = await import('node:fs');
|
|
267
340
|
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'))) {
|
|
341
|
+
// Load the p12 certificate and the passphrase
|
|
268
342
|
try {
|
|
269
343
|
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12'));
|
|
270
344
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.p12')}`);
|
|
@@ -276,7 +350,7 @@ export class Frontend extends EventEmitter {
|
|
|
276
350
|
}
|
|
277
351
|
try {
|
|
278
352
|
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass'), 'utf8');
|
|
279
|
-
passphrase = passphrase.trim();
|
|
353
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
280
354
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pass')}`);
|
|
281
355
|
}
|
|
282
356
|
catch (error) {
|
|
@@ -287,6 +361,7 @@ export class Frontend extends EventEmitter {
|
|
|
287
361
|
httpsServerOptions = { pfx, passphrase };
|
|
288
362
|
}
|
|
289
363
|
else {
|
|
364
|
+
// 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
365
|
try {
|
|
291
366
|
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem'), 'utf8');
|
|
292
367
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs', 'cert.pem')}`);
|
|
@@ -316,9 +391,10 @@ export class Frontend extends EventEmitter {
|
|
|
316
391
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
317
392
|
}
|
|
318
393
|
if (hasParameter('mtls')) {
|
|
319
|
-
httpsServerOptions.requestCert = true;
|
|
320
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
394
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
395
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
321
396
|
}
|
|
397
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
322
398
|
const https = await import('node:https');
|
|
323
399
|
try {
|
|
324
400
|
this.log.debug(`Creating HTTPS server...`);
|
|
@@ -329,6 +405,7 @@ export class Frontend extends EventEmitter {
|
|
|
329
405
|
this.emit('server_error', error);
|
|
330
406
|
return;
|
|
331
407
|
}
|
|
408
|
+
// Listen on the specified port
|
|
332
409
|
if (hasParameter('ingress')) {
|
|
333
410
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
334
411
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -348,23 +425,29 @@ export class Frontend extends EventEmitter {
|
|
|
348
425
|
}
|
|
349
426
|
this.httpsServer.on('upgrade', async (req, socket, head) => {
|
|
350
427
|
try {
|
|
428
|
+
// Only proceed for real WebSocket upgrades
|
|
429
|
+
// istanbul ignore next cause is only a safety check
|
|
351
430
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
352
431
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
353
432
|
return socket.destroy();
|
|
354
433
|
}
|
|
434
|
+
// Build a URL so we can read ?password=...
|
|
355
435
|
const url = new URL(req.url ?? '/', `https://${req.headers.host || 'localhost'}`);
|
|
436
|
+
// Validate WebSocket password
|
|
356
437
|
const password = url.searchParams.get('password') ?? '';
|
|
357
438
|
if (password !== this.storedPassword) {
|
|
358
439
|
this.log.error(`WebSocket upgrade error: Invalid password ${password ? '[redacted]' : '(empty)'}`);
|
|
359
440
|
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
360
441
|
return socket.destroy();
|
|
361
442
|
}
|
|
443
|
+
// Complete the WebSocket handshake
|
|
362
444
|
this.log.debug(`WebSocket upgrade success host ${url.host} password ${password ? '[redacted]' : '(empty)'}`);
|
|
363
445
|
this.webSocketServer?.handleUpgrade(req, socket, head, (ws) => {
|
|
364
446
|
this.webSocketServer?.emit('connection', ws, req);
|
|
365
447
|
});
|
|
366
448
|
}
|
|
367
449
|
catch (err) {
|
|
450
|
+
/* istanbul ignore next: only triggered on unexpected internal error */
|
|
368
451
|
{
|
|
369
452
|
inspectError(this.log, 'WebSocket upgrade error:', err);
|
|
370
453
|
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
@@ -386,6 +469,7 @@ export class Frontend extends EventEmitter {
|
|
|
386
469
|
return;
|
|
387
470
|
});
|
|
388
471
|
}
|
|
472
|
+
// Subscribe to cli events
|
|
389
473
|
cliEmitter.removeAllListeners();
|
|
390
474
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
391
475
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -396,6 +480,8 @@ export class Frontend extends EventEmitter {
|
|
|
396
480
|
cliEmitter.on('cpu', (cpuUsage, processCpuUsage) => {
|
|
397
481
|
this.wssSendCpuUpdate(cpuUsage, processCpuUsage);
|
|
398
482
|
});
|
|
483
|
+
// Endpoint to validate login code
|
|
484
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
399
485
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
400
486
|
const { password } = req.body;
|
|
401
487
|
this.log.debug(`The frontend sent /api/login with password ${password ? '[redacted]' : '(empty)'}`);
|
|
@@ -408,17 +494,20 @@ export class Frontend extends EventEmitter {
|
|
|
408
494
|
res.json({ valid: false });
|
|
409
495
|
}
|
|
410
496
|
});
|
|
497
|
+
// Endpoint to provide health check for docker
|
|
411
498
|
this.expressApp.get('/health', (req, res) => {
|
|
412
499
|
this.log.debug('Express received /health');
|
|
413
500
|
const healthStatus = {
|
|
414
|
-
status: 'ok',
|
|
415
|
-
uptime: process.uptime(),
|
|
416
|
-
timestamp: new Date().toISOString(),
|
|
501
|
+
status: 'ok', // Indicate service is healthy
|
|
502
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
503
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
417
504
|
};
|
|
418
505
|
res.status(200).json(healthStatus);
|
|
419
506
|
});
|
|
507
|
+
// Endpoint to provide memory usage details
|
|
420
508
|
this.expressApp.get('/memory', async (req, res) => {
|
|
421
509
|
this.log.debug('Express received /memory');
|
|
510
|
+
// Memory usage from process
|
|
422
511
|
const memoryUsageRaw = process.memoryUsage();
|
|
423
512
|
const memoryUsage = {
|
|
424
513
|
rss: formatBytes(memoryUsageRaw.rss),
|
|
@@ -427,10 +516,13 @@ export class Frontend extends EventEmitter {
|
|
|
427
516
|
external: formatBytes(memoryUsageRaw.external),
|
|
428
517
|
arrayBuffers: formatBytes(memoryUsageRaw.arrayBuffers),
|
|
429
518
|
};
|
|
519
|
+
// V8 heap statistics
|
|
430
520
|
const { default: v8 } = await import('node:v8');
|
|
431
521
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
432
522
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
523
|
+
// Format heapStats
|
|
433
524
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatBytes(value)]));
|
|
525
|
+
// Format heapSpaces
|
|
434
526
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
435
527
|
...space,
|
|
436
528
|
space_size: formatBytes(space.space_size),
|
|
@@ -449,18 +541,22 @@ export class Frontend extends EventEmitter {
|
|
|
449
541
|
};
|
|
450
542
|
res.status(200).json(memoryReport);
|
|
451
543
|
});
|
|
544
|
+
// Endpoint to provide settings
|
|
452
545
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
453
546
|
this.log.debug('The frontend sent /api/settings');
|
|
454
547
|
res.json(await this.getApiSettings());
|
|
455
548
|
});
|
|
549
|
+
// Endpoint to provide plugins
|
|
456
550
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
457
551
|
this.log.debug('The frontend sent /api/plugins');
|
|
458
552
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
459
553
|
});
|
|
554
|
+
// Endpoint to provide devices
|
|
460
555
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
461
556
|
this.log.debug('The frontend sent /api/devices');
|
|
462
557
|
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
463
558
|
});
|
|
559
|
+
// Endpoint to view the matterbridge log
|
|
464
560
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
465
561
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
466
562
|
try {
|
|
@@ -474,6 +570,7 @@ export class Frontend extends EventEmitter {
|
|
|
474
570
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
475
571
|
}
|
|
476
572
|
});
|
|
573
|
+
// Endpoint to view the matter.js log
|
|
477
574
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
478
575
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
479
576
|
try {
|
|
@@ -487,6 +584,7 @@ export class Frontend extends EventEmitter {
|
|
|
487
584
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
488
585
|
}
|
|
489
586
|
});
|
|
587
|
+
// Endpoint to view the diagnostic.log
|
|
490
588
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
491
589
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
492
590
|
await this.generateDiagnostic();
|
|
@@ -497,10 +595,13 @@ export class Frontend extends EventEmitter {
|
|
|
497
595
|
res.send(data.slice(29));
|
|
498
596
|
}
|
|
499
597
|
catch (error) {
|
|
598
|
+
// istanbul ignore next
|
|
500
599
|
this.log.error(`Error reading diagnostic log file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
600
|
+
// istanbul ignore next
|
|
501
601
|
res.status(500).send('Error reading diagnostic log file.');
|
|
502
602
|
}
|
|
503
603
|
});
|
|
604
|
+
// Endpoint to download the diagnostic.log
|
|
504
605
|
this.expressApp.get('/api/download-diagnostic', async (req, res) => {
|
|
505
606
|
this.log.debug(`The frontend sent /api/download-diagnostic`);
|
|
506
607
|
await this.generateDiagnostic();
|
|
@@ -511,16 +612,19 @@ export class Frontend extends EventEmitter {
|
|
|
511
612
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
|
|
512
613
|
}
|
|
513
614
|
catch (error) {
|
|
615
|
+
// istanbul ignore next
|
|
514
616
|
this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
|
|
515
617
|
}
|
|
516
618
|
res.type('text/plain');
|
|
517
619
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
|
|
620
|
+
/* istanbul ignore if */
|
|
518
621
|
if (error) {
|
|
519
622
|
this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
520
623
|
res.status(500).send('Error downloading the diagnostic log file');
|
|
521
624
|
}
|
|
522
625
|
});
|
|
523
626
|
});
|
|
627
|
+
// Endpoint to view the history.html
|
|
524
628
|
this.expressApp.get('/api/viewhistory', async (req, res) => {
|
|
525
629
|
this.log.debug('The frontend sent /api/viewhistory');
|
|
526
630
|
try {
|
|
@@ -534,6 +638,7 @@ export class Frontend extends EventEmitter {
|
|
|
534
638
|
res.status(500).send('Error reading history file.');
|
|
535
639
|
}
|
|
536
640
|
});
|
|
641
|
+
// Endpoint to download the history.html
|
|
537
642
|
this.expressApp.get('/api/downloadhistory', async (req, res) => {
|
|
538
643
|
this.log.debug(`The frontend sent /api/downloadhistory`);
|
|
539
644
|
try {
|
|
@@ -543,6 +648,7 @@ export class Frontend extends EventEmitter {
|
|
|
543
648
|
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
|
|
544
649
|
res.type('text/plain');
|
|
545
650
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
|
|
651
|
+
/* istanbul ignore if */
|
|
546
652
|
if (error) {
|
|
547
653
|
this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
548
654
|
res.status(500).send('Error downloading history file');
|
|
@@ -554,6 +660,7 @@ export class Frontend extends EventEmitter {
|
|
|
554
660
|
res.status(500).send('Error reading history file.');
|
|
555
661
|
}
|
|
556
662
|
});
|
|
663
|
+
// Endpoint to view the shelly log
|
|
557
664
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
558
665
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
559
666
|
try {
|
|
@@ -567,6 +674,7 @@ export class Frontend extends EventEmitter {
|
|
|
567
674
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
568
675
|
}
|
|
569
676
|
});
|
|
677
|
+
// Endpoint to download the matterbridge log
|
|
570
678
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
571
679
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
572
680
|
const fs = await import('node:fs');
|
|
@@ -581,12 +689,14 @@ export class Frontend extends EventEmitter {
|
|
|
581
689
|
}
|
|
582
690
|
res.type('text/plain');
|
|
583
691
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
692
|
+
/* istanbul ignore if */
|
|
584
693
|
if (error) {
|
|
585
694
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
586
695
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
587
696
|
}
|
|
588
697
|
});
|
|
589
698
|
});
|
|
699
|
+
// Endpoint to download the matter log
|
|
590
700
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
591
701
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
592
702
|
const fs = await import('node:fs');
|
|
@@ -601,12 +711,14 @@ export class Frontend extends EventEmitter {
|
|
|
601
711
|
}
|
|
602
712
|
res.type('text/plain');
|
|
603
713
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
714
|
+
/* istanbul ignore if */
|
|
604
715
|
if (error) {
|
|
605
716
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
606
717
|
res.status(500).send('Error downloading the matter log file');
|
|
607
718
|
}
|
|
608
719
|
});
|
|
609
720
|
});
|
|
721
|
+
// Endpoint to download the shelly log
|
|
610
722
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
611
723
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
612
724
|
const fs = await import('node:fs');
|
|
@@ -621,75 +733,91 @@ export class Frontend extends EventEmitter {
|
|
|
621
733
|
}
|
|
622
734
|
res.type('text/plain');
|
|
623
735
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
736
|
+
/* istanbul ignore if */
|
|
624
737
|
if (error) {
|
|
625
738
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
626
739
|
res.status(500).send('Error downloading Shelly system log file');
|
|
627
740
|
}
|
|
628
741
|
});
|
|
629
742
|
});
|
|
743
|
+
// Endpoint to download the matterbridge storage directory
|
|
630
744
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
631
745
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
632
746
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
633
747
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
748
|
+
/* istanbul ignore if */
|
|
634
749
|
if (error) {
|
|
635
750
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
636
751
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
637
752
|
}
|
|
638
753
|
});
|
|
639
754
|
});
|
|
755
|
+
// Endpoint to download the matter storage file
|
|
640
756
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
641
757
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
642
758
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
643
759
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
760
|
+
/* istanbul ignore if */
|
|
644
761
|
if (error) {
|
|
645
762
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
646
763
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
647
764
|
}
|
|
648
765
|
});
|
|
649
766
|
});
|
|
767
|
+
// Endpoint to download the matterbridge plugin directory
|
|
650
768
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
651
769
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
652
770
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
653
771
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
772
|
+
/* istanbul ignore if */
|
|
654
773
|
if (error) {
|
|
655
774
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
656
775
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
657
776
|
}
|
|
658
777
|
});
|
|
659
778
|
});
|
|
779
|
+
// Endpoint to download the matterbridge plugin config files
|
|
660
780
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
661
781
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
662
782
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
663
783
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
784
|
+
/* istanbul ignore if */
|
|
664
785
|
if (error) {
|
|
665
786
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
666
787
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
667
788
|
}
|
|
668
789
|
});
|
|
669
790
|
});
|
|
791
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
670
792
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
671
793
|
this.log.debug('The frontend sent /api/download-backup');
|
|
672
794
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
795
|
+
/* istanbul ignore if */
|
|
673
796
|
if (error) {
|
|
674
797
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
675
798
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
676
799
|
}
|
|
677
800
|
});
|
|
678
801
|
});
|
|
802
|
+
// Endpoint to upload a package
|
|
679
803
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
680
804
|
const { filename } = req.body;
|
|
681
805
|
const file = req.file;
|
|
806
|
+
/* istanbul ignore if */
|
|
682
807
|
if (!file || !filename) {
|
|
683
808
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
684
809
|
res.status(400).send('Invalid request: file and filename are required');
|
|
685
810
|
return;
|
|
686
811
|
}
|
|
687
812
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
813
|
+
// Define the path where the plugin file will be saved
|
|
688
814
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
689
815
|
try {
|
|
816
|
+
// Move the uploaded file to the specified path
|
|
690
817
|
const fs = await import('node:fs');
|
|
691
818
|
await fs.promises.rename(file.path, filePath);
|
|
692
819
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
820
|
+
// Install the plugin package
|
|
693
821
|
if (filename.endsWith('.tgz')) {
|
|
694
822
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
695
823
|
if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
|
|
@@ -717,6 +845,7 @@ export class Frontend extends EventEmitter {
|
|
|
717
845
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
718
846
|
}
|
|
719
847
|
});
|
|
848
|
+
// Fallback for routing (must be the last route)
|
|
720
849
|
this.expressApp.use((req, res) => {
|
|
721
850
|
const filePath = path.resolve(this.matterbridge.rootDirectory, 'frontend', 'build');
|
|
722
851
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html in ${filePath} as fallback`);
|
|
@@ -727,13 +856,16 @@ export class Frontend extends EventEmitter {
|
|
|
727
856
|
async stop() {
|
|
728
857
|
this.log.debug('Stopping the frontend...');
|
|
729
858
|
const ws = await import('ws');
|
|
859
|
+
// Remove listeners from the express app
|
|
730
860
|
if (this.expressApp) {
|
|
731
861
|
this.expressApp.removeAllListeners();
|
|
732
862
|
this.expressApp = undefined;
|
|
733
863
|
this.log.debug('Frontend app closed successfully');
|
|
734
864
|
}
|
|
865
|
+
// Close the WebSocket server
|
|
735
866
|
if (this.webSocketServer) {
|
|
736
867
|
this.log.debug('Closing WebSocket server...');
|
|
868
|
+
// Close all active connections
|
|
737
869
|
this.webSocketServer.clients.forEach((client) => {
|
|
738
870
|
if (client.readyState === ws.WebSocket.OPEN) {
|
|
739
871
|
client.close();
|
|
@@ -742,6 +874,7 @@ export class Frontend extends EventEmitter {
|
|
|
742
874
|
await withTimeout(new Promise((resolve) => {
|
|
743
875
|
this.webSocketServer?.close((error) => {
|
|
744
876
|
if (error) {
|
|
877
|
+
// istanbul ignore next
|
|
745
878
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
746
879
|
}
|
|
747
880
|
else {
|
|
@@ -754,8 +887,27 @@ export class Frontend extends EventEmitter {
|
|
|
754
887
|
this.webSocketServer.removeAllListeners();
|
|
755
888
|
this.webSocketServer = undefined;
|
|
756
889
|
}
|
|
890
|
+
// Close the http server
|
|
757
891
|
if (this.httpServer) {
|
|
758
892
|
this.log.debug('Closing http server...');
|
|
893
|
+
/*
|
|
894
|
+
await withTimeout(
|
|
895
|
+
new Promise<void>((resolve) => {
|
|
896
|
+
this.httpServer?.close((error) => {
|
|
897
|
+
if (error) {
|
|
898
|
+
// istanbul ignore next
|
|
899
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
900
|
+
} else {
|
|
901
|
+
this.log.debug('Http server closed successfully');
|
|
902
|
+
this.emit('server_stopped');
|
|
903
|
+
}
|
|
904
|
+
resolve();
|
|
905
|
+
});
|
|
906
|
+
}),
|
|
907
|
+
5000,
|
|
908
|
+
false,
|
|
909
|
+
);
|
|
910
|
+
*/
|
|
759
911
|
this.httpServer.close();
|
|
760
912
|
this.log.debug('Http server closed successfully');
|
|
761
913
|
this.listening = false;
|
|
@@ -764,8 +916,27 @@ export class Frontend extends EventEmitter {
|
|
|
764
916
|
this.httpServer = undefined;
|
|
765
917
|
this.log.debug('Frontend http server closed successfully');
|
|
766
918
|
}
|
|
919
|
+
// Close the https server
|
|
767
920
|
if (this.httpsServer) {
|
|
768
921
|
this.log.debug('Closing https server...');
|
|
922
|
+
/*
|
|
923
|
+
await withTimeout(
|
|
924
|
+
new Promise<void>((resolve) => {
|
|
925
|
+
this.httpsServer?.close((error) => {
|
|
926
|
+
if (error) {
|
|
927
|
+
// istanbul ignore next
|
|
928
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
929
|
+
} else {
|
|
930
|
+
this.log.debug('Https server closed successfully');
|
|
931
|
+
this.emit('server_stopped');
|
|
932
|
+
}
|
|
933
|
+
resolve();
|
|
934
|
+
});
|
|
935
|
+
}),
|
|
936
|
+
5000,
|
|
937
|
+
false,
|
|
938
|
+
);
|
|
939
|
+
*/
|
|
769
940
|
this.httpsServer.close();
|
|
770
941
|
this.log.debug('Https server closed successfully');
|
|
771
942
|
this.listening = false;
|
|
@@ -776,7 +947,13 @@ export class Frontend extends EventEmitter {
|
|
|
776
947
|
}
|
|
777
948
|
this.log.debug('Frontend stopped successfully');
|
|
778
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Retrieves the api settings data.
|
|
952
|
+
*
|
|
953
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
954
|
+
*/
|
|
779
955
|
async getApiSettings() {
|
|
956
|
+
// Update the variable system information properties
|
|
780
957
|
this.matterbridge.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
781
958
|
this.matterbridge.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
782
959
|
this.matterbridge.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -786,6 +963,7 @@ export class Frontend extends EventEmitter {
|
|
|
786
963
|
this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
787
964
|
this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
788
965
|
this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
966
|
+
// Create the matterbridge information
|
|
789
967
|
const info = {
|
|
790
968
|
homeDirectory: this.matterbridge.homeDirectory,
|
|
791
969
|
rootDirectory: this.matterbridge.rootDirectory,
|
|
@@ -821,9 +999,15 @@ export class Frontend extends EventEmitter {
|
|
|
821
999
|
};
|
|
822
1000
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
823
1001
|
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Retrieves the reachable attribute.
|
|
1004
|
+
*
|
|
1005
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
1006
|
+
* @returns {boolean} The reachable attribute.
|
|
1007
|
+
*/
|
|
824
1008
|
getReachability(device) {
|
|
825
1009
|
if (this.matterbridge.hasCleanupStarted)
|
|
826
|
-
return false;
|
|
1010
|
+
return false; // Skip if cleanup has started
|
|
827
1011
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
828
1012
|
return false;
|
|
829
1013
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -834,9 +1018,15 @@ export class Frontend extends EventEmitter {
|
|
|
834
1018
|
return true;
|
|
835
1019
|
return false;
|
|
836
1020
|
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Retrieves the power source attribute.
|
|
1023
|
+
*
|
|
1024
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
1025
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
1026
|
+
*/
|
|
837
1027
|
getPowerSource(endpoint) {
|
|
838
1028
|
if (this.matterbridge.hasCleanupStarted)
|
|
839
|
-
return;
|
|
1029
|
+
return; // Skip if cleanup has started
|
|
840
1030
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
841
1031
|
return undefined;
|
|
842
1032
|
const powerSource = (device) => {
|
|
@@ -851,16 +1041,25 @@ export class Frontend extends EventEmitter {
|
|
|
851
1041
|
}
|
|
852
1042
|
return;
|
|
853
1043
|
};
|
|
1044
|
+
// Root endpoint
|
|
854
1045
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
855
1046
|
return powerSource(endpoint);
|
|
1047
|
+
// Child endpoints
|
|
856
1048
|
for (const child of endpoint.getChildEndpoints()) {
|
|
857
1049
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
858
1050
|
return powerSource(child);
|
|
859
1051
|
}
|
|
860
1052
|
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Retrieves the cluster text description from a given device.
|
|
1055
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
1056
|
+
*
|
|
1057
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
1058
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1059
|
+
*/
|
|
861
1060
|
getClusterTextFromDevice(device) {
|
|
862
1061
|
if (this.matterbridge.hasCleanupStarted)
|
|
863
|
-
return '';
|
|
1062
|
+
return ''; // Skip if cleanup has started
|
|
864
1063
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
865
1064
|
return '';
|
|
866
1065
|
const getUserLabel = (device) => {
|
|
@@ -870,6 +1069,7 @@ export class Frontend extends EventEmitter {
|
|
|
870
1069
|
if (composed)
|
|
871
1070
|
return 'Composed: ' + composed.value;
|
|
872
1071
|
}
|
|
1072
|
+
// istanbul ignore next cause is not reachable
|
|
873
1073
|
return '';
|
|
874
1074
|
};
|
|
875
1075
|
const getFixedLabel = (device) => {
|
|
@@ -879,11 +1079,13 @@ export class Frontend extends EventEmitter {
|
|
|
879
1079
|
if (composed)
|
|
880
1080
|
return 'Composed: ' + composed.value;
|
|
881
1081
|
}
|
|
1082
|
+
// istanbul ignore next cause is not reacheable
|
|
882
1083
|
return '';
|
|
883
1084
|
};
|
|
884
1085
|
let attributes = '';
|
|
885
1086
|
let supportedModes = [];
|
|
886
1087
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1088
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
887
1089
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
888
1090
|
return;
|
|
889
1091
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -973,11 +1175,17 @@ export class Frontend extends EventEmitter {
|
|
|
973
1175
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
974
1176
|
attributes += `${getUserLabel(device)} `;
|
|
975
1177
|
});
|
|
1178
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
976
1179
|
return attributes.trimStart().trimEnd();
|
|
977
1180
|
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1183
|
+
*
|
|
1184
|
+
* @returns {ApiPlugin[]} An array of BaseRegisteredPlugin.
|
|
1185
|
+
*/
|
|
978
1186
|
getPlugins() {
|
|
979
1187
|
if (this.matterbridge.hasCleanupStarted)
|
|
980
|
-
return [];
|
|
1188
|
+
return []; // Skip if cleanup has started
|
|
981
1189
|
const plugins = [];
|
|
982
1190
|
for (const plugin of this.matterbridge.plugins.array()) {
|
|
983
1191
|
plugins.push({
|
|
@@ -1005,18 +1213,27 @@ export class Frontend extends EventEmitter {
|
|
|
1005
1213
|
schemaJson: plugin.schemaJson,
|
|
1006
1214
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1007
1215
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1216
|
+
// Childbridge mode specific data
|
|
1008
1217
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1009
1218
|
});
|
|
1010
1219
|
}
|
|
1011
1220
|
return plugins;
|
|
1012
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Retrieves the devices from Matterbridge.
|
|
1224
|
+
*
|
|
1225
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1226
|
+
* @returns {ApiDevice[]} An array of ApiDevices for the frontend.
|
|
1227
|
+
*/
|
|
1013
1228
|
getDevices(pluginName) {
|
|
1014
1229
|
if (this.matterbridge.hasCleanupStarted)
|
|
1015
|
-
return [];
|
|
1230
|
+
return []; // Skip if cleanup has started
|
|
1016
1231
|
const devices = [];
|
|
1017
1232
|
for (const device of this.matterbridge.devices.array()) {
|
|
1233
|
+
// Filter by pluginName if provided
|
|
1018
1234
|
if (pluginName && pluginName !== device.plugin)
|
|
1019
1235
|
continue;
|
|
1236
|
+
// Check if the device has the required properties
|
|
1020
1237
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1021
1238
|
continue;
|
|
1022
1239
|
devices.push({
|
|
@@ -1036,24 +1253,39 @@ export class Frontend extends EventEmitter {
|
|
|
1036
1253
|
}
|
|
1037
1254
|
return devices;
|
|
1038
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1258
|
+
*
|
|
1259
|
+
* Response for /api/clusters
|
|
1260
|
+
*
|
|
1261
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1262
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1263
|
+
* @returns {ApiClusters | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1264
|
+
*/
|
|
1039
1265
|
getClusters(pluginName, endpointNumber) {
|
|
1040
1266
|
if (this.matterbridge.hasCleanupStarted)
|
|
1041
|
-
return;
|
|
1267
|
+
return; // Skip if cleanup has started
|
|
1042
1268
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1043
1269
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1044
1270
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1045
1271
|
return;
|
|
1046
1272
|
}
|
|
1273
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1274
|
+
// Get the device types from the main endpoint
|
|
1047
1275
|
const deviceTypes = [];
|
|
1048
1276
|
const clusters = [];
|
|
1049
1277
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1050
1278
|
deviceTypes.push(d.deviceType);
|
|
1051
1279
|
});
|
|
1280
|
+
// Get the clusters from the main endpoint
|
|
1052
1281
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1053
1282
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1054
1283
|
return;
|
|
1055
1284
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1056
1285
|
return;
|
|
1286
|
+
// console.log(
|
|
1287
|
+
// `${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}`,
|
|
1288
|
+
// );
|
|
1057
1289
|
clusters.push({
|
|
1058
1290
|
endpoint: endpoint.number.toString(),
|
|
1059
1291
|
number: endpoint.number,
|
|
@@ -1067,12 +1299,19 @@ export class Frontend extends EventEmitter {
|
|
|
1067
1299
|
attributeLocalValue: attributeValue,
|
|
1068
1300
|
});
|
|
1069
1301
|
});
|
|
1302
|
+
// Get the child endpoints
|
|
1070
1303
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1304
|
+
// if (childEndpoints.length === 0) {
|
|
1305
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1306
|
+
// }
|
|
1071
1307
|
childEndpoints.forEach((childEndpoint) => {
|
|
1308
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1072
1309
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1073
1310
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1074
1311
|
return;
|
|
1075
1312
|
}
|
|
1313
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1314
|
+
// Get the device types of the child endpoint
|
|
1076
1315
|
const deviceTypes = [];
|
|
1077
1316
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1078
1317
|
deviceTypes.push(d.deviceType);
|
|
@@ -1082,6 +1321,9 @@ export class Frontend extends EventEmitter {
|
|
|
1082
1321
|
return;
|
|
1083
1322
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1084
1323
|
return;
|
|
1324
|
+
// console.log(
|
|
1325
|
+
// `${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}`,
|
|
1326
|
+
// );
|
|
1085
1327
|
clusters.push({
|
|
1086
1328
|
endpoint: childEndpoint.number.toString(),
|
|
1087
1329
|
number: childEndpoint.number,
|
|
@@ -1101,6 +1343,7 @@ export class Frontend extends EventEmitter {
|
|
|
1101
1343
|
async generateDiagnostic() {
|
|
1102
1344
|
this.log.debug('Generating diagnostic...');
|
|
1103
1345
|
const serverNodes = [];
|
|
1346
|
+
// istanbul ignore else
|
|
1104
1347
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
1105
1348
|
if (this.matterbridge.serverNode)
|
|
1106
1349
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -1111,6 +1354,7 @@ export class Frontend extends EventEmitter {
|
|
|
1111
1354
|
serverNodes.push(plugin.serverNode);
|
|
1112
1355
|
}
|
|
1113
1356
|
}
|
|
1357
|
+
// istanbul ignore next
|
|
1114
1358
|
for (const device of this.matterbridge.devices.array()) {
|
|
1115
1359
|
if (device.serverNode)
|
|
1116
1360
|
serverNodes.push(device.serverNode);
|
|
@@ -1134,8 +1378,15 @@ export class Frontend extends EventEmitter {
|
|
|
1134
1378
|
values: [...serverNodes],
|
|
1135
1379
|
})));
|
|
1136
1380
|
delete Logger.destinations.diagnostic;
|
|
1137
|
-
await wait(500);
|
|
1381
|
+
await wait(500); // Wait for the log to be written
|
|
1138
1382
|
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1385
|
+
*
|
|
1386
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1387
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1388
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1389
|
+
*/
|
|
1139
1390
|
async wsMessageHandler(client, message) {
|
|
1140
1391
|
let data;
|
|
1141
1392
|
const sendResponse = (data) => {
|
|
@@ -1153,12 +1404,13 @@ export class Frontend extends EventEmitter {
|
|
|
1153
1404
|
client.send(JSON.stringify(data));
|
|
1154
1405
|
}
|
|
1155
1406
|
else {
|
|
1407
|
+
// istanbul ignore next cause is only a safety check
|
|
1156
1408
|
this.log.error('Cannot send api response, client not connected');
|
|
1157
1409
|
}
|
|
1158
1410
|
};
|
|
1159
1411
|
try {
|
|
1160
1412
|
data = JSON.parse(message.toString());
|
|
1161
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1413
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
1162
1414
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1163
1415
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1164
1416
|
return;
|
|
@@ -1215,7 +1467,22 @@ export class Frontend extends EventEmitter {
|
|
|
1215
1467
|
}
|
|
1216
1468
|
this.wssSendSnackbarMessage(`Adding plugin ${data.params.pluginNameOrPath}...`, 5);
|
|
1217
1469
|
this.log.debug(`Adding plugin ${data.params.pluginNameOrPath}...`);
|
|
1218
|
-
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, '');
|
|
1470
|
+
data.params.pluginNameOrPath = data.params.pluginNameOrPath.replace(/@.*$/, ''); // Remove @version if present
|
|
1471
|
+
/*
|
|
1472
|
+
const plugin = (await this.server.fetch({ type: 'plugins_add', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginNameOrPath } }, 5000)).response.plugin;
|
|
1473
|
+
if (plugin) {
|
|
1474
|
+
this.wssSendSnackbarMessage(`Added plugin ${data.params.pluginNameOrPath}`, 5, 'success');
|
|
1475
|
+
await this.server.fetch({ type: 'plugins_load', src: this.server.name, dst: 'plugins', params: { plugin: plugin.name } }, 5000);
|
|
1476
|
+
this.wssSendRestartRequired();
|
|
1477
|
+
this.wssSendRefreshRequired('plugins');
|
|
1478
|
+
this.wssSendRefreshRequired('devices');
|
|
1479
|
+
this.wssSendSnackbarMessage(`Loaded plugin ${localData.params.pluginNameOrPath}`, 5, 'success');
|
|
1480
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1481
|
+
} else {
|
|
1482
|
+
this.wssSendSnackbarMessage(`Plugin ${data.params.pluginNameOrPath} not added`, 10, 'error');
|
|
1483
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Plugin ${data.params.pluginNameOrPath} not added` });
|
|
1484
|
+
}
|
|
1485
|
+
*/
|
|
1219
1486
|
const plugin = await this.matterbridge.plugins.add(data.params.pluginNameOrPath);
|
|
1220
1487
|
if (plugin) {
|
|
1221
1488
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1229,6 +1496,7 @@ export class Frontend extends EventEmitter {
|
|
|
1229
1496
|
return;
|
|
1230
1497
|
})
|
|
1231
1498
|
.catch((_error) => {
|
|
1499
|
+
//
|
|
1232
1500
|
});
|
|
1233
1501
|
}
|
|
1234
1502
|
else {
|
|
@@ -1243,6 +1511,10 @@ export class Frontend extends EventEmitter {
|
|
|
1243
1511
|
}
|
|
1244
1512
|
this.wssSendSnackbarMessage(`Removing plugin ${data.params.pluginName}...`, 5);
|
|
1245
1513
|
this.log.debug(`Removing plugin ${data.params.pluginName}...`);
|
|
1514
|
+
/*
|
|
1515
|
+
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);
|
|
1516
|
+
await this.server.fetch({ type: 'plugins_remove', src: this.server.name, dst: 'plugins', params: { nameOrPath: data.params.pluginName } }, 5000);
|
|
1517
|
+
*/
|
|
1246
1518
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1247
1519
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1248
1520
|
await this.matterbridge.plugins.remove(data.params.pluginName);
|
|
@@ -1278,6 +1550,7 @@ export class Frontend extends EventEmitter {
|
|
|
1278
1550
|
return;
|
|
1279
1551
|
})
|
|
1280
1552
|
.catch((_error) => {
|
|
1553
|
+
//
|
|
1281
1554
|
});
|
|
1282
1555
|
}
|
|
1283
1556
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1303,6 +1576,7 @@ export class Frontend extends EventEmitter {
|
|
|
1303
1576
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1304
1577
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1305
1578
|
if (plugin.serverNode) {
|
|
1579
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1306
1580
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1307
1581
|
plugin.serverNode = undefined;
|
|
1308
1582
|
}
|
|
@@ -1312,18 +1586,20 @@ export class Frontend extends EventEmitter {
|
|
|
1312
1586
|
this.matterbridge.devices.remove(device);
|
|
1313
1587
|
}
|
|
1314
1588
|
}
|
|
1589
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1315
1590
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1316
1591
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1317
1592
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1318
|
-
plugin.restartRequired = false;
|
|
1593
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1319
1594
|
let needRestart = 0;
|
|
1320
1595
|
for (const plugin of this.matterbridge.plugins) {
|
|
1321
1596
|
if (plugin.restartRequired)
|
|
1322
1597
|
needRestart++;
|
|
1323
1598
|
}
|
|
1324
1599
|
if (needRestart === 0) {
|
|
1325
|
-
this.wssSendRestartNotRequired(true);
|
|
1600
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1326
1601
|
}
|
|
1602
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1327
1603
|
if (plugin.serverNode)
|
|
1328
1604
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1329
1605
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1468,6 +1744,9 @@ export class Frontend extends EventEmitter {
|
|
|
1468
1744
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1469
1745
|
}
|
|
1470
1746
|
if (data.params.advertise) {
|
|
1747
|
+
// TODO: matter.js 0.16.0
|
|
1748
|
+
// await serverNode.env.get(DeviceAdvertiser)?.advertise(true);
|
|
1749
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1471
1750
|
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1472
1751
|
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1473
1752
|
await advertiser.advertise(true);
|
|
@@ -1593,22 +1872,22 @@ export class Frontend extends EventEmitter {
|
|
|
1593
1872
|
if (isValidString(data.params.value, 4)) {
|
|
1594
1873
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1595
1874
|
if (data.params.value === 'Debug') {
|
|
1596
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1875
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1597
1876
|
}
|
|
1598
1877
|
else if (data.params.value === 'Info') {
|
|
1599
|
-
await this.matterbridge.setLogLevel("info");
|
|
1878
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1600
1879
|
}
|
|
1601
1880
|
else if (data.params.value === 'Notice') {
|
|
1602
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1881
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1603
1882
|
}
|
|
1604
1883
|
else if (data.params.value === 'Warn') {
|
|
1605
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1884
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1606
1885
|
}
|
|
1607
1886
|
else if (data.params.value === 'Error') {
|
|
1608
|
-
await this.matterbridge.setLogLevel("error");
|
|
1887
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1609
1888
|
}
|
|
1610
1889
|
else if (data.params.value === 'Fatal') {
|
|
1611
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1890
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1612
1891
|
}
|
|
1613
1892
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1614
1893
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1619,6 +1898,7 @@ export class Frontend extends EventEmitter {
|
|
|
1619
1898
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1620
1899
|
this.matterbridge.fileLogger = data.params.value;
|
|
1621
1900
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1901
|
+
// Create the file logger for matterbridge
|
|
1622
1902
|
if (data.params.value)
|
|
1623
1903
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1624
1904
|
else
|
|
@@ -1648,11 +1928,12 @@ export class Frontend extends EventEmitter {
|
|
|
1648
1928
|
Logger.level = MatterLogLevel.FATAL;
|
|
1649
1929
|
}
|
|
1650
1930
|
this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1931
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1932
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1933
|
+
if (this.matterbridge.getLogLevel() === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1934
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1935
|
+
if (this.matterbridge.getLogLevel() === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1936
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
1656
1937
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1657
1938
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1658
1939
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
@@ -1704,6 +1985,7 @@ export class Frontend extends EventEmitter {
|
|
|
1704
1985
|
}
|
|
1705
1986
|
break;
|
|
1706
1987
|
case 'setmatterport':
|
|
1988
|
+
// eslint-disable-next-line no-case-declarations
|
|
1707
1989
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1708
1990
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1709
1991
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1723,6 +2005,7 @@ export class Frontend extends EventEmitter {
|
|
|
1723
2005
|
}
|
|
1724
2006
|
break;
|
|
1725
2007
|
case 'setmatterdiscriminator':
|
|
2008
|
+
// eslint-disable-next-line no-case-declarations
|
|
1726
2009
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1727
2010
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1728
2011
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1742,6 +2025,7 @@ export class Frontend extends EventEmitter {
|
|
|
1742
2025
|
}
|
|
1743
2026
|
break;
|
|
1744
2027
|
case 'setmatterpasscode':
|
|
2028
|
+
// eslint-disable-next-line no-case-declarations
|
|
1745
2029
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1746
2030
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1747
2031
|
this.matterbridge.passcode = passcode;
|
|
@@ -1787,15 +2071,19 @@ export class Frontend extends EventEmitter {
|
|
|
1787
2071
|
return;
|
|
1788
2072
|
}
|
|
1789
2073
|
const config = plugin.configJson;
|
|
2074
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1790
2075
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2076
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1791
2077
|
if (select === 'serial')
|
|
1792
2078
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1793
2079
|
if (select === 'name')
|
|
1794
2080
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1795
2081
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
2082
|
+
// Remove postfix from the serial if it exists
|
|
1796
2083
|
if (config.postfix) {
|
|
1797
2084
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1798
2085
|
}
|
|
2086
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1799
2087
|
if (isValidArray(config.whiteList, 1)) {
|
|
1800
2088
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1801
2089
|
config.whiteList.push(data.params.serial);
|
|
@@ -1804,6 +2092,7 @@ export class Frontend extends EventEmitter {
|
|
|
1804
2092
|
config.whiteList.push(data.params.name);
|
|
1805
2093
|
}
|
|
1806
2094
|
}
|
|
2095
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1807
2096
|
if (isValidArray(config.blackList, 1)) {
|
|
1808
2097
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1809
2098
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1831,7 +2120,9 @@ export class Frontend extends EventEmitter {
|
|
|
1831
2120
|
return;
|
|
1832
2121
|
}
|
|
1833
2122
|
const config = plugin.configJson;
|
|
2123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1834
2124
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
2125
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1835
2126
|
if (select === 'serial')
|
|
1836
2127
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1837
2128
|
if (select === 'name')
|
|
@@ -1840,6 +2131,7 @@ export class Frontend extends EventEmitter {
|
|
|
1840
2131
|
if (config.postfix) {
|
|
1841
2132
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1842
2133
|
}
|
|
2134
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1843
2135
|
if (isValidArray(config.whiteList, 1)) {
|
|
1844
2136
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1845
2137
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1848,6 +2140,7 @@ export class Frontend extends EventEmitter {
|
|
|
1848
2140
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1849
2141
|
}
|
|
1850
2142
|
}
|
|
2143
|
+
// Add the serial to the blackList
|
|
1851
2144
|
if (isValidArray(config.blackList)) {
|
|
1852
2145
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1853
2146
|
config.blackList.push(data.params.serial);
|
|
@@ -1870,6 +2163,7 @@ export class Frontend extends EventEmitter {
|
|
|
1870
2163
|
}
|
|
1871
2164
|
}
|
|
1872
2165
|
else {
|
|
2166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1873
2167
|
const localData = data;
|
|
1874
2168
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1875
2169
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1879,23 +2173,46 @@ export class Frontend extends EventEmitter {
|
|
|
1879
2173
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1880
2174
|
}
|
|
1881
2175
|
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
2178
|
+
*
|
|
2179
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2180
|
+
* @param {string} time - The time string of the message
|
|
2181
|
+
* @param {string} name - The logger name of the message
|
|
2182
|
+
* @param {string} message - The content of the message.
|
|
2183
|
+
*
|
|
2184
|
+
* @remarks
|
|
2185
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
2186
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
2187
|
+
* The function sends the message to all connected clients.
|
|
2188
|
+
*/
|
|
1882
2189
|
wssSendLogMessage(level, time, name, message) {
|
|
1883
2190
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1884
2191
|
return;
|
|
1885
2192
|
if (!level || !time || !name || !message)
|
|
1886
2193
|
return;
|
|
2194
|
+
// Remove ANSI escape codes from the message
|
|
2195
|
+
// eslint-disable-next-line no-control-regex
|
|
1887
2196
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2197
|
+
// Remove leading asterisks from the message
|
|
1888
2198
|
message = message.replace(/^\*+/, '');
|
|
2199
|
+
// Replace all occurrences of \t and \n
|
|
1889
2200
|
message = message.replace(/[\t\n]/g, '');
|
|
2201
|
+
// Remove non-printable characters
|
|
2202
|
+
// eslint-disable-next-line no-control-regex
|
|
1890
2203
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2204
|
+
// Replace all occurrences of \" with "
|
|
1891
2205
|
message = message.replace(/\\"/g, '"');
|
|
2206
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1892
2207
|
const maxContinuousLength = 100;
|
|
1893
2208
|
const keepStartLength = 20;
|
|
1894
2209
|
const keepEndLength = 20;
|
|
2210
|
+
// Split the message into words
|
|
1895
2211
|
if (level !== 'spawn') {
|
|
1896
2212
|
message = message
|
|
1897
2213
|
.split(' ')
|
|
1898
2214
|
.map((word) => {
|
|
2215
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1899
2216
|
if (word.length > maxContinuousLength) {
|
|
1900
2217
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1901
2218
|
}
|
|
@@ -1903,14 +2220,34 @@ export class Frontend extends EventEmitter {
|
|
|
1903
2220
|
})
|
|
1904
2221
|
.join(' ');
|
|
1905
2222
|
}
|
|
2223
|
+
// Send the message to all connected clients
|
|
1906
2224
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1907
2225
|
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2228
|
+
*
|
|
2229
|
+
* @param {string} changed - The changed value.
|
|
2230
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2231
|
+
* possible values for changed:
|
|
2232
|
+
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2233
|
+
* - 'plugins'
|
|
2234
|
+
* - 'devices'
|
|
2235
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2236
|
+
* @param {ApiMatter} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2237
|
+
*/
|
|
1908
2238
|
wssSendRefreshRequired(changed, params) {
|
|
1909
2239
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1910
2240
|
return;
|
|
1911
2241
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2242
|
+
// Send the message to all connected clients
|
|
1912
2243
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1913
2244
|
}
|
|
2245
|
+
/**
|
|
2246
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2247
|
+
*
|
|
2248
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2249
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2250
|
+
*/
|
|
1914
2251
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1915
2252
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1916
2253
|
return;
|
|
@@ -1919,8 +2256,14 @@ export class Frontend extends EventEmitter {
|
|
|
1919
2256
|
this.matterbridge.fixedRestartRequired = fixed;
|
|
1920
2257
|
if (snackbar === true)
|
|
1921
2258
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2259
|
+
// Send the message to all connected clients
|
|
1922
2260
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1923
2261
|
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2264
|
+
*
|
|
2265
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2266
|
+
*/
|
|
1924
2267
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1925
2268
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1926
2269
|
return;
|
|
@@ -1928,57 +2271,133 @@ export class Frontend extends EventEmitter {
|
|
|
1928
2271
|
this.matterbridge.restartRequired = false;
|
|
1929
2272
|
if (snackbar === true)
|
|
1930
2273
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2274
|
+
// Send the message to all connected clients
|
|
1931
2275
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1932
2276
|
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2279
|
+
*
|
|
2280
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2281
|
+
*/
|
|
1933
2282
|
wssSendUpdateRequired(devVersion = false) {
|
|
1934
2283
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1935
2284
|
return;
|
|
1936
2285
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1937
2286
|
this.matterbridge.updateRequired = true;
|
|
2287
|
+
// Send the message to all connected clients
|
|
1938
2288
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1939
2289
|
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Sends a cpu update message to all connected clients.
|
|
2292
|
+
*
|
|
2293
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2294
|
+
* @param {number} processCpuUsage - The CPU usage percentage of the process to send.
|
|
2295
|
+
*/
|
|
1940
2296
|
wssSendCpuUpdate(cpuUsage, processCpuUsage) {
|
|
1941
2297
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1942
2298
|
return;
|
|
1943
2299
|
if (hasParameter('debug'))
|
|
1944
2300
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2301
|
+
// Send the message to all connected clients
|
|
1945
2302
|
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 } });
|
|
1946
2303
|
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Sends a memory update message to all connected clients.
|
|
2306
|
+
*
|
|
2307
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2308
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2309
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2310
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2311
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2312
|
+
* @param {string} external - The external memory in bytes.
|
|
2313
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2314
|
+
*/
|
|
1947
2315
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1948
2316
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1949
2317
|
return;
|
|
1950
2318
|
if (hasParameter('debug'))
|
|
1951
2319
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2320
|
+
// Send the message to all connected clients
|
|
1952
2321
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1953
2322
|
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Sends an uptime update message to all connected clients.
|
|
2325
|
+
*
|
|
2326
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2327
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2328
|
+
*/
|
|
1954
2329
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1955
2330
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1956
2331
|
return;
|
|
1957
2332
|
if (hasParameter('debug'))
|
|
1958
2333
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2334
|
+
// Send the message to all connected clients
|
|
1959
2335
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1960
2336
|
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Sends an open snackbar message to all connected clients.
|
|
2339
|
+
*
|
|
2340
|
+
* @param {string} message - The message to send.
|
|
2341
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2342
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2343
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2344
|
+
*
|
|
2345
|
+
* @remarks
|
|
2346
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2347
|
+
*/
|
|
1961
2348
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1962
2349
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1963
2350
|
return;
|
|
1964
2351
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2352
|
+
// Send the message to all connected clients
|
|
1965
2353
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1966
2354
|
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Sends a close snackbar message to all connected clients.
|
|
2357
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2358
|
+
*
|
|
2359
|
+
* @param {string} message - The message to send.
|
|
2360
|
+
*/
|
|
1967
2361
|
wssSendCloseSnackbarMessage(message) {
|
|
1968
2362
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1969
2363
|
return;
|
|
1970
2364
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2365
|
+
// Send the message to all connected clients
|
|
1971
2366
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1972
2367
|
}
|
|
2368
|
+
/**
|
|
2369
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2370
|
+
*
|
|
2371
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2372
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2373
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2374
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2375
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2376
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2377
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2378
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2379
|
+
*
|
|
2380
|
+
* @remarks
|
|
2381
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2382
|
+
* with the updated attribute information.
|
|
2383
|
+
*/
|
|
1973
2384
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1974
2385
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1975
2386
|
return;
|
|
1976
2387
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2388
|
+
// Send the message to all connected clients
|
|
1977
2389
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1978
2390
|
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Sends a message to all connected clients.
|
|
2393
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2394
|
+
*
|
|
2395
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2396
|
+
*/
|
|
1979
2397
|
wssBroadcastMessage(msg) {
|
|
1980
2398
|
if (!this.listening || this.webSocketServer?.clients.size === 0)
|
|
1981
2399
|
return;
|
|
2400
|
+
// Send the message to all connected clients
|
|
1982
2401
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1983
2402
|
if (msg.method !== 'log')
|
|
1984
2403
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -1989,3 +2408,4 @@ export class Frontend extends EventEmitter {
|
|
|
1989
2408
|
});
|
|
1990
2409
|
}
|
|
1991
2410
|
}
|
|
2411
|
+
//# sourceMappingURL=frontend.js.map
|