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