matterbridge 3.2.8-dev-20250920-1a6178d → 3.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +60 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +55 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +25 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +228 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +429 -38
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +572 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +51 -3
- package/dist/frontendTypes.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +59 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +47 -0
- package/dist/globalMatterbridge.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +442 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +775 -50
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1747 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +65 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +761 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +630 -17
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +36 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1515 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1373 -55
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +345 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +380 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +304 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +190 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +270 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +249 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +172 -11
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +99 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +13 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/hex.d.ts +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/jestHelpers.d.ts +137 -0
- package/dist/utils/jestHelpers.d.ts.map +1 -0
- package/dist/utils/jestHelpers.js +153 -3
- package/dist/utils/jestHelpers.js.map +1 -0
- package/dist/utils/network.d.ts +84 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +91 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +33 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +68 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +54 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/frontend/build/assets/index.js +1 -1
- package/frontend/build/index.html +12 -12
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
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
|
+
// Node modules
|
|
1
25
|
import { createServer } from 'node:http';
|
|
2
26
|
import https from 'node:https';
|
|
3
27
|
import os from 'node:os';
|
|
@@ -5,13 +29,17 @@ import path from 'node:path';
|
|
|
5
29
|
import { existsSync, promises as fs, unlinkSync } from 'node:fs';
|
|
6
30
|
import EventEmitter from 'node:events';
|
|
7
31
|
import { appendFile } from 'node:fs/promises';
|
|
32
|
+
// Third-party modules
|
|
8
33
|
import express from 'express';
|
|
9
34
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
10
35
|
import multer from 'multer';
|
|
36
|
+
// AnsiLogger module
|
|
11
37
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
38
|
+
// @matter
|
|
12
39
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
|
|
13
40
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
14
41
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
|
|
42
|
+
// Matterbridge
|
|
15
43
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
|
|
16
44
|
import { plg } from './matterbridgeTypes.js';
|
|
17
45
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
@@ -27,7 +55,7 @@ export class Frontend extends EventEmitter {
|
|
|
27
55
|
constructor(matterbridge) {
|
|
28
56
|
super();
|
|
29
57
|
this.matterbridge = matterbridge;
|
|
30
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
58
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
31
59
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
32
60
|
}
|
|
33
61
|
set logLevel(logLevel) {
|
|
@@ -36,10 +64,39 @@ export class Frontend extends EventEmitter {
|
|
|
36
64
|
async start(port = 8283) {
|
|
37
65
|
this.port = port;
|
|
38
66
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
39
|
-
|
|
67
|
+
// Initialize multer with the upload directory
|
|
68
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
40
69
|
const upload = multer({ dest: uploadDir });
|
|
70
|
+
// Create the express app that serves the frontend
|
|
41
71
|
this.expressApp = express();
|
|
72
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
73
|
+
/*
|
|
74
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
75
|
+
for (const method of methods) {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
80
|
+
try {
|
|
81
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
82
|
+
return original(path, ...rest);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
*/
|
|
90
|
+
// Log all requests to the server for debugging
|
|
91
|
+
/*
|
|
92
|
+
this.expressApp.use((req, res, next) => {
|
|
93
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
94
|
+
next();
|
|
95
|
+
});
|
|
96
|
+
*/
|
|
97
|
+
// Serve static files from '/static' endpoint
|
|
42
98
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
99
|
+
// Read the package.json file to get the frontend version
|
|
43
100
|
try {
|
|
44
101
|
this.log.debug(`Reading frontend package.json...`);
|
|
45
102
|
const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
|
|
@@ -47,9 +104,11 @@ export class Frontend extends EventEmitter {
|
|
|
47
104
|
this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
|
|
48
105
|
}
|
|
49
106
|
catch (error) {
|
|
107
|
+
// istanbul ignore next
|
|
50
108
|
this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
109
|
}
|
|
52
110
|
if (!hasParameter('ssl')) {
|
|
111
|
+
// Create an HTTP server and attach the express app
|
|
53
112
|
try {
|
|
54
113
|
this.log.debug(`Creating HTTP server...`);
|
|
55
114
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -59,6 +118,7 @@ export class Frontend extends EventEmitter {
|
|
|
59
118
|
this.emit('server_error', error);
|
|
60
119
|
return;
|
|
61
120
|
}
|
|
121
|
+
// Listen on the specified port
|
|
62
122
|
if (hasParameter('ingress')) {
|
|
63
123
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
64
124
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -97,6 +157,7 @@ export class Frontend extends EventEmitter {
|
|
|
97
157
|
let passphrase;
|
|
98
158
|
let httpsServerOptions = {};
|
|
99
159
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
160
|
+
// Load the p12 certificate and the passphrase
|
|
100
161
|
try {
|
|
101
162
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
102
163
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -108,7 +169,7 @@ export class Frontend extends EventEmitter {
|
|
|
108
169
|
}
|
|
109
170
|
try {
|
|
110
171
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
111
|
-
passphrase = passphrase.trim();
|
|
172
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
112
173
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
113
174
|
}
|
|
114
175
|
catch (error) {
|
|
@@ -119,6 +180,7 @@ export class Frontend extends EventEmitter {
|
|
|
119
180
|
httpsServerOptions = { pfx, passphrase };
|
|
120
181
|
}
|
|
121
182
|
else {
|
|
183
|
+
// 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.
|
|
122
184
|
try {
|
|
123
185
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
124
186
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -148,9 +210,10 @@ export class Frontend extends EventEmitter {
|
|
|
148
210
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
149
211
|
}
|
|
150
212
|
if (hasParameter('mtls')) {
|
|
151
|
-
httpsServerOptions.requestCert = true;
|
|
152
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
213
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
214
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
153
215
|
}
|
|
216
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
154
217
|
try {
|
|
155
218
|
this.log.debug(`Creating HTTPS server...`);
|
|
156
219
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -160,6 +223,7 @@ export class Frontend extends EventEmitter {
|
|
|
160
223
|
this.emit('server_error', error);
|
|
161
224
|
return;
|
|
162
225
|
}
|
|
226
|
+
// Listen on the specified port
|
|
163
227
|
if (hasParameter('ingress')) {
|
|
164
228
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
165
229
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -189,17 +253,19 @@ export class Frontend extends EventEmitter {
|
|
|
189
253
|
return;
|
|
190
254
|
});
|
|
191
255
|
}
|
|
256
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
192
257
|
const wssPort = this.port;
|
|
193
258
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
194
259
|
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
195
260
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
196
261
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
197
262
|
const clientIp = request.socket.remoteAddress;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
263
|
+
// Set the global logger callback for the WebSocketServer
|
|
264
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
265
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
266
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
267
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
268
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
203
269
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
204
270
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
205
271
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -221,6 +287,7 @@ export class Frontend extends EventEmitter {
|
|
|
221
287
|
}
|
|
222
288
|
});
|
|
223
289
|
ws.on('error', (error) => {
|
|
290
|
+
// istanbul ignore next
|
|
224
291
|
this.log.error(`WebSocket client error: ${error}`);
|
|
225
292
|
});
|
|
226
293
|
});
|
|
@@ -234,6 +301,7 @@ export class Frontend extends EventEmitter {
|
|
|
234
301
|
this.webSocketServer.on('error', (ws, error) => {
|
|
235
302
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
236
303
|
});
|
|
304
|
+
// Subscribe to cli events
|
|
237
305
|
cliEmitter.removeAllListeners();
|
|
238
306
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
239
307
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -244,6 +312,8 @@ export class Frontend extends EventEmitter {
|
|
|
244
312
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
245
313
|
this.wssSendCpuUpdate(cpuUsage);
|
|
246
314
|
});
|
|
315
|
+
// Endpoint to validate login code
|
|
316
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
247
317
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
248
318
|
const { password } = req.body;
|
|
249
319
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -262,23 +332,27 @@ export class Frontend extends EventEmitter {
|
|
|
262
332
|
this.log.warn('/api/login error wrong password');
|
|
263
333
|
res.json({ valid: false });
|
|
264
334
|
}
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
265
336
|
}
|
|
266
337
|
catch (error) {
|
|
267
338
|
this.log.error('/api/login error getting password');
|
|
268
339
|
res.json({ valid: false });
|
|
269
340
|
}
|
|
270
341
|
});
|
|
342
|
+
// Endpoint to provide health check for docker
|
|
271
343
|
this.expressApp.get('/health', (req, res) => {
|
|
272
344
|
this.log.debug('Express received /health');
|
|
273
345
|
const healthStatus = {
|
|
274
|
-
status: 'ok',
|
|
275
|
-
uptime: process.uptime(),
|
|
276
|
-
timestamp: new Date().toISOString(),
|
|
346
|
+
status: 'ok', // Indicate service is healthy
|
|
347
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
348
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
277
349
|
};
|
|
278
350
|
res.status(200).json(healthStatus);
|
|
279
351
|
});
|
|
352
|
+
// Endpoint to provide memory usage details
|
|
280
353
|
this.expressApp.get('/memory', async (req, res) => {
|
|
281
354
|
this.log.debug('Express received /memory');
|
|
355
|
+
// Memory usage from process
|
|
282
356
|
const memoryUsageRaw = process.memoryUsage();
|
|
283
357
|
const memoryUsage = {
|
|
284
358
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -287,10 +361,13 @@ export class Frontend extends EventEmitter {
|
|
|
287
361
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
288
362
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
289
363
|
};
|
|
364
|
+
// V8 heap statistics
|
|
290
365
|
const { default: v8 } = await import('node:v8');
|
|
291
366
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
292
367
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
368
|
+
// Format heapStats
|
|
293
369
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
370
|
+
// Format heapSpaces
|
|
294
371
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
295
372
|
...space,
|
|
296
373
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -308,19 +385,23 @@ export class Frontend extends EventEmitter {
|
|
|
308
385
|
};
|
|
309
386
|
res.status(200).json(memoryReport);
|
|
310
387
|
});
|
|
388
|
+
// Endpoint to provide settings
|
|
311
389
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
312
390
|
this.log.debug('The frontend sent /api/settings');
|
|
313
391
|
res.json(await this.getApiSettings());
|
|
314
392
|
});
|
|
393
|
+
// Endpoint to provide plugins
|
|
315
394
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
316
395
|
this.log.debug('The frontend sent /api/plugins');
|
|
317
396
|
res.json(this.getPlugins());
|
|
318
397
|
});
|
|
398
|
+
// Endpoint to provide devices
|
|
319
399
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
320
400
|
this.log.debug('The frontend sent /api/devices');
|
|
321
401
|
const devices = await this.getDevices();
|
|
322
402
|
res.json(devices);
|
|
323
403
|
});
|
|
404
|
+
// Endpoint to view the matterbridge log
|
|
324
405
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
325
406
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
326
407
|
try {
|
|
@@ -333,6 +414,7 @@ export class Frontend extends EventEmitter {
|
|
|
333
414
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
334
415
|
}
|
|
335
416
|
});
|
|
417
|
+
// Endpoint to view the matter.js log
|
|
336
418
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
337
419
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
338
420
|
try {
|
|
@@ -345,9 +427,11 @@ export class Frontend extends EventEmitter {
|
|
|
345
427
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
346
428
|
}
|
|
347
429
|
});
|
|
430
|
+
// Endpoint to view the diagnostic.log
|
|
348
431
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
349
432
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
350
433
|
const serverNodes = [];
|
|
434
|
+
// istanbul ignore else
|
|
351
435
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
352
436
|
if (this.matterbridge.serverNode)
|
|
353
437
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -358,6 +442,7 @@ export class Frontend extends EventEmitter {
|
|
|
358
442
|
serverNodes.push(plugin.serverNode);
|
|
359
443
|
}
|
|
360
444
|
}
|
|
445
|
+
// istanbul ignore next
|
|
361
446
|
for (const device of this.matterbridge.getDevices()) {
|
|
362
447
|
if (device.serverNode)
|
|
363
448
|
serverNodes.push(device.serverNode);
|
|
@@ -380,17 +465,20 @@ export class Frontend extends EventEmitter {
|
|
|
380
465
|
values: [...serverNodes],
|
|
381
466
|
})));
|
|
382
467
|
delete Logger.destinations.diagnostic;
|
|
383
|
-
await wait(500);
|
|
468
|
+
await wait(500); // Wait for the log to be written
|
|
384
469
|
try {
|
|
385
470
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
|
|
386
471
|
res.type('text/plain');
|
|
387
472
|
res.send(data.slice(29));
|
|
388
473
|
}
|
|
389
474
|
catch (error) {
|
|
475
|
+
// istanbul ignore next
|
|
390
476
|
this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
477
|
+
// istanbul ignore next
|
|
391
478
|
res.status(500).send('Error reading diagnostic log file.');
|
|
392
479
|
}
|
|
393
480
|
});
|
|
481
|
+
// Endpoint to view the shelly log
|
|
394
482
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
395
483
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
396
484
|
try {
|
|
@@ -403,6 +491,7 @@ export class Frontend extends EventEmitter {
|
|
|
403
491
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
404
492
|
}
|
|
405
493
|
});
|
|
494
|
+
// Endpoint to download the matterbridge log
|
|
406
495
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
407
496
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
408
497
|
try {
|
|
@@ -416,12 +505,14 @@ export class Frontend extends EventEmitter {
|
|
|
416
505
|
}
|
|
417
506
|
res.type('text/plain');
|
|
418
507
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
508
|
+
/* istanbul ignore if */
|
|
419
509
|
if (error) {
|
|
420
510
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
421
511
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
422
512
|
}
|
|
423
513
|
});
|
|
424
514
|
});
|
|
515
|
+
// Endpoint to download the matter log
|
|
425
516
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
426
517
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
427
518
|
try {
|
|
@@ -435,12 +526,14 @@ export class Frontend extends EventEmitter {
|
|
|
435
526
|
}
|
|
436
527
|
res.type('text/plain');
|
|
437
528
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
529
|
+
/* istanbul ignore if */
|
|
438
530
|
if (error) {
|
|
439
531
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
440
532
|
res.status(500).send('Error downloading the matter log file');
|
|
441
533
|
}
|
|
442
534
|
});
|
|
443
535
|
});
|
|
536
|
+
// Endpoint to download the shelly log
|
|
444
537
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
445
538
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
446
539
|
try {
|
|
@@ -454,74 +547,90 @@ export class Frontend extends EventEmitter {
|
|
|
454
547
|
}
|
|
455
548
|
res.type('text/plain');
|
|
456
549
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
550
|
+
/* istanbul ignore if */
|
|
457
551
|
if (error) {
|
|
458
552
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
459
553
|
res.status(500).send('Error downloading Shelly system log file');
|
|
460
554
|
}
|
|
461
555
|
});
|
|
462
556
|
});
|
|
557
|
+
// Endpoint to download the matterbridge storage directory
|
|
463
558
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
464
559
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
465
560
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
466
561
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
562
|
+
/* istanbul ignore if */
|
|
467
563
|
if (error) {
|
|
468
564
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
469
565
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
470
566
|
}
|
|
471
567
|
});
|
|
472
568
|
});
|
|
569
|
+
// Endpoint to download the matter storage file
|
|
473
570
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
474
571
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
475
572
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
476
573
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
574
|
+
/* istanbul ignore if */
|
|
477
575
|
if (error) {
|
|
478
576
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
479
577
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
480
578
|
}
|
|
481
579
|
});
|
|
482
580
|
});
|
|
581
|
+
// Endpoint to download the matterbridge plugin directory
|
|
483
582
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
484
583
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
485
584
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
486
585
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
586
|
+
/* istanbul ignore if */
|
|
487
587
|
if (error) {
|
|
488
588
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
489
589
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
490
590
|
}
|
|
491
591
|
});
|
|
492
592
|
});
|
|
593
|
+
// Endpoint to download the matterbridge plugin config files
|
|
493
594
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
494
595
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
495
596
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
496
597
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
598
|
+
/* istanbul ignore if */
|
|
497
599
|
if (error) {
|
|
498
600
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
499
601
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
500
602
|
}
|
|
501
603
|
});
|
|
502
604
|
});
|
|
605
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
503
606
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
504
607
|
this.log.debug('The frontend sent /api/download-backup');
|
|
505
608
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
609
|
+
/* istanbul ignore if */
|
|
506
610
|
if (error) {
|
|
507
611
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
508
612
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
509
613
|
}
|
|
510
614
|
});
|
|
511
615
|
});
|
|
616
|
+
// Endpoint to upload a package
|
|
512
617
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
513
618
|
const { filename } = req.body;
|
|
514
619
|
const file = req.file;
|
|
620
|
+
/* istanbul ignore if */
|
|
515
621
|
if (!file || !filename) {
|
|
516
622
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
517
623
|
res.status(400).send('Invalid request: file and filename are required');
|
|
518
624
|
return;
|
|
519
625
|
}
|
|
520
626
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
627
|
+
// Define the path where the plugin file will be saved
|
|
521
628
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
522
629
|
try {
|
|
630
|
+
// Move the uploaded file to the specified path
|
|
523
631
|
await fs.rename(file.path, filePath);
|
|
524
632
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
633
|
+
// Install the plugin package
|
|
525
634
|
if (filename.endsWith('.tgz')) {
|
|
526
635
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
527
636
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -541,6 +650,7 @@ export class Frontend extends EventEmitter {
|
|
|
541
650
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
542
651
|
}
|
|
543
652
|
});
|
|
653
|
+
// Fallback for routing (must be the last route)
|
|
544
654
|
this.expressApp.use((req, res) => {
|
|
545
655
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
546
656
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -549,13 +659,16 @@ export class Frontend extends EventEmitter {
|
|
|
549
659
|
}
|
|
550
660
|
async stop() {
|
|
551
661
|
this.log.debug('Stopping the frontend...');
|
|
662
|
+
// Remove listeners from the express app
|
|
552
663
|
if (this.expressApp) {
|
|
553
664
|
this.expressApp.removeAllListeners();
|
|
554
665
|
this.expressApp = undefined;
|
|
555
666
|
this.log.debug('Frontend app closed successfully');
|
|
556
667
|
}
|
|
668
|
+
// Close the WebSocket server
|
|
557
669
|
if (this.webSocketServer) {
|
|
558
670
|
this.log.debug('Closing WebSocket server...');
|
|
671
|
+
// Close all active connections
|
|
559
672
|
this.webSocketServer.clients.forEach((client) => {
|
|
560
673
|
if (client.readyState === WebSocket.OPEN) {
|
|
561
674
|
client.close();
|
|
@@ -564,6 +677,7 @@ export class Frontend extends EventEmitter {
|
|
|
564
677
|
await withTimeout(new Promise((resolve) => {
|
|
565
678
|
this.webSocketServer?.close((error) => {
|
|
566
679
|
if (error) {
|
|
680
|
+
// istanbul ignore next
|
|
567
681
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
568
682
|
}
|
|
569
683
|
else {
|
|
@@ -576,8 +690,27 @@ export class Frontend extends EventEmitter {
|
|
|
576
690
|
this.webSocketServer.removeAllListeners();
|
|
577
691
|
this.webSocketServer = undefined;
|
|
578
692
|
}
|
|
693
|
+
// Close the http server
|
|
579
694
|
if (this.httpServer) {
|
|
580
695
|
this.log.debug('Closing http server...');
|
|
696
|
+
/*
|
|
697
|
+
await withTimeout(
|
|
698
|
+
new Promise<void>((resolve) => {
|
|
699
|
+
this.httpServer?.close((error) => {
|
|
700
|
+
if (error) {
|
|
701
|
+
// istanbul ignore next
|
|
702
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
703
|
+
} else {
|
|
704
|
+
this.log.debug('Http server closed successfully');
|
|
705
|
+
this.emit('server_stopped');
|
|
706
|
+
}
|
|
707
|
+
resolve();
|
|
708
|
+
});
|
|
709
|
+
}),
|
|
710
|
+
5000,
|
|
711
|
+
false,
|
|
712
|
+
);
|
|
713
|
+
*/
|
|
581
714
|
this.httpServer.close();
|
|
582
715
|
this.log.debug('Http server closed successfully');
|
|
583
716
|
this.emit('server_stopped');
|
|
@@ -585,8 +718,27 @@ export class Frontend extends EventEmitter {
|
|
|
585
718
|
this.httpServer = undefined;
|
|
586
719
|
this.log.debug('Frontend http server closed successfully');
|
|
587
720
|
}
|
|
721
|
+
// Close the https server
|
|
588
722
|
if (this.httpsServer) {
|
|
589
723
|
this.log.debug('Closing https server...');
|
|
724
|
+
/*
|
|
725
|
+
await withTimeout(
|
|
726
|
+
new Promise<void>((resolve) => {
|
|
727
|
+
this.httpsServer?.close((error) => {
|
|
728
|
+
if (error) {
|
|
729
|
+
// istanbul ignore next
|
|
730
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
731
|
+
} else {
|
|
732
|
+
this.log.debug('Https server closed successfully');
|
|
733
|
+
this.emit('server_stopped');
|
|
734
|
+
}
|
|
735
|
+
resolve();
|
|
736
|
+
});
|
|
737
|
+
}),
|
|
738
|
+
5000,
|
|
739
|
+
false,
|
|
740
|
+
);
|
|
741
|
+
*/
|
|
590
742
|
this.httpsServer.close();
|
|
591
743
|
this.log.debug('Https server closed successfully');
|
|
592
744
|
this.emit('server_stopped');
|
|
@@ -596,6 +748,7 @@ export class Frontend extends EventEmitter {
|
|
|
596
748
|
}
|
|
597
749
|
this.log.debug('Frontend stopped successfully');
|
|
598
750
|
}
|
|
751
|
+
// Function to format bytes to KB, MB, or GB
|
|
599
752
|
formatMemoryUsage = (bytes) => {
|
|
600
753
|
if (bytes >= 1024 ** 3) {
|
|
601
754
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -607,6 +760,7 @@ export class Frontend extends EventEmitter {
|
|
|
607
760
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
608
761
|
}
|
|
609
762
|
};
|
|
763
|
+
// Function to format system uptime with only the most significant unit
|
|
610
764
|
formatOsUpTime = (seconds) => {
|
|
611
765
|
if (seconds >= 86400) {
|
|
612
766
|
const days = Math.floor(seconds / 86400);
|
|
@@ -622,7 +776,13 @@ export class Frontend extends EventEmitter {
|
|
|
622
776
|
}
|
|
623
777
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
624
778
|
};
|
|
779
|
+
/**
|
|
780
|
+
* Retrieves the api settings data.
|
|
781
|
+
*
|
|
782
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
783
|
+
*/
|
|
625
784
|
async getApiSettings() {
|
|
785
|
+
// Update the system information
|
|
626
786
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
627
787
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
628
788
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -631,6 +791,7 @@ export class Frontend extends EventEmitter {
|
|
|
631
791
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
632
792
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
633
793
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
794
|
+
// Update the matterbridge information
|
|
634
795
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
635
796
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
636
797
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -644,6 +805,12 @@ export class Frontend extends EventEmitter {
|
|
|
644
805
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
645
806
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
646
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* Retrieves the reachable attribute.
|
|
810
|
+
*
|
|
811
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
812
|
+
* @returns {boolean} The reachable attribute.
|
|
813
|
+
*/
|
|
647
814
|
getReachability(device) {
|
|
648
815
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
649
816
|
return false;
|
|
@@ -655,6 +822,12 @@ export class Frontend extends EventEmitter {
|
|
|
655
822
|
return true;
|
|
656
823
|
return false;
|
|
657
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Retrieves the power source attribute.
|
|
827
|
+
*
|
|
828
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
829
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
830
|
+
*/
|
|
658
831
|
getPowerSource(endpoint) {
|
|
659
832
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
660
833
|
return undefined;
|
|
@@ -670,13 +843,22 @@ export class Frontend extends EventEmitter {
|
|
|
670
843
|
}
|
|
671
844
|
return;
|
|
672
845
|
};
|
|
846
|
+
// Root endpoint
|
|
673
847
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
674
848
|
return powerSource(endpoint);
|
|
849
|
+
// Child endpoints
|
|
675
850
|
for (const child of endpoint.getChildEndpoints()) {
|
|
676
851
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
677
852
|
return powerSource(child);
|
|
678
853
|
}
|
|
679
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Retrieves the cluster text description from a given device.
|
|
857
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
858
|
+
*
|
|
859
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
860
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
861
|
+
*/
|
|
680
862
|
getClusterTextFromDevice(device) {
|
|
681
863
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
682
864
|
return '';
|
|
@@ -687,6 +869,7 @@ export class Frontend extends EventEmitter {
|
|
|
687
869
|
if (composed)
|
|
688
870
|
return 'Composed: ' + composed.value;
|
|
689
871
|
}
|
|
872
|
+
// istanbul ignore next cause is not reachable
|
|
690
873
|
return '';
|
|
691
874
|
};
|
|
692
875
|
const getFixedLabel = (device) => {
|
|
@@ -696,11 +879,13 @@ export class Frontend extends EventEmitter {
|
|
|
696
879
|
if (composed)
|
|
697
880
|
return 'Composed: ' + composed.value;
|
|
698
881
|
}
|
|
882
|
+
// istanbul ignore next cause is not reacheable
|
|
699
883
|
return '';
|
|
700
884
|
};
|
|
701
885
|
let attributes = '';
|
|
702
886
|
let supportedModes = [];
|
|
703
887
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
888
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
704
889
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
705
890
|
return;
|
|
706
891
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -790,11 +975,17 @@ export class Frontend extends EventEmitter {
|
|
|
790
975
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
791
976
|
attributes += `${getUserLabel(device)} `;
|
|
792
977
|
});
|
|
978
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
793
979
|
return attributes.trimStart().trimEnd();
|
|
794
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
983
|
+
*
|
|
984
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
985
|
+
*/
|
|
795
986
|
getPlugins() {
|
|
796
987
|
if (this.matterbridge.hasCleanupStarted)
|
|
797
|
-
return [];
|
|
988
|
+
return []; // Skip if cleanup has started
|
|
798
989
|
const baseRegisteredPlugins = [];
|
|
799
990
|
for (const plugin of this.matterbridge.plugins) {
|
|
800
991
|
baseRegisteredPlugins.push({
|
|
@@ -822,18 +1013,27 @@ export class Frontend extends EventEmitter {
|
|
|
822
1013
|
schemaJson: plugin.schemaJson,
|
|
823
1014
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
824
1015
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1016
|
+
// Childbridge mode specific data
|
|
825
1017
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
826
1018
|
});
|
|
827
1019
|
}
|
|
828
1020
|
return baseRegisteredPlugins;
|
|
829
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Retrieves the devices from Matterbridge.
|
|
1024
|
+
*
|
|
1025
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1026
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1027
|
+
*/
|
|
830
1028
|
async getDevices(pluginName) {
|
|
831
1029
|
if (this.matterbridge.hasCleanupStarted)
|
|
832
|
-
return [];
|
|
1030
|
+
return []; // Skip if cleanup has started
|
|
833
1031
|
const devices = [];
|
|
834
1032
|
for (const device of this.matterbridge.devices.array()) {
|
|
1033
|
+
// Filter by pluginName if provided
|
|
835
1034
|
if (pluginName && pluginName !== device.plugin)
|
|
836
1035
|
continue;
|
|
1036
|
+
// Check if the device has the required properties
|
|
837
1037
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
838
1038
|
continue;
|
|
839
1039
|
devices.push({
|
|
@@ -853,22 +1053,37 @@ export class Frontend extends EventEmitter {
|
|
|
853
1053
|
}
|
|
854
1054
|
return devices;
|
|
855
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1058
|
+
*
|
|
1059
|
+
* Response for /api/clusters
|
|
1060
|
+
*
|
|
1061
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1062
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1063
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1064
|
+
*/
|
|
856
1065
|
getClusters(pluginName, endpointNumber) {
|
|
857
1066
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
858
1067
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
859
1068
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
860
1069
|
return;
|
|
861
1070
|
}
|
|
1071
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1072
|
+
// Get the device types from the main endpoint
|
|
862
1073
|
const deviceTypes = [];
|
|
863
1074
|
const clusters = [];
|
|
864
1075
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
865
1076
|
deviceTypes.push(d.deviceType);
|
|
866
1077
|
});
|
|
1078
|
+
// Get the clusters from the main endpoint
|
|
867
1079
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
868
1080
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
869
1081
|
return;
|
|
870
1082
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
871
1083
|
return;
|
|
1084
|
+
// console.log(
|
|
1085
|
+
// `${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}`,
|
|
1086
|
+
// );
|
|
872
1087
|
clusters.push({
|
|
873
1088
|
endpoint: endpoint.number.toString(),
|
|
874
1089
|
id: 'main',
|
|
@@ -881,12 +1096,19 @@ export class Frontend extends EventEmitter {
|
|
|
881
1096
|
attributeLocalValue: attributeValue,
|
|
882
1097
|
});
|
|
883
1098
|
});
|
|
1099
|
+
// Get the child endpoints
|
|
884
1100
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1101
|
+
// if (childEndpoints.length === 0) {
|
|
1102
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1103
|
+
// }
|
|
885
1104
|
childEndpoints.forEach((childEndpoint) => {
|
|
1105
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
886
1106
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
887
1107
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
888
1108
|
return;
|
|
889
1109
|
}
|
|
1110
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1111
|
+
// Get the device types of the child endpoint
|
|
890
1112
|
const deviceTypes = [];
|
|
891
1113
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
892
1114
|
deviceTypes.push(d.deviceType);
|
|
@@ -896,9 +1118,12 @@ export class Frontend extends EventEmitter {
|
|
|
896
1118
|
return;
|
|
897
1119
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
898
1120
|
return;
|
|
1121
|
+
// console.log(
|
|
1122
|
+
// `${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}`,
|
|
1123
|
+
// );
|
|
899
1124
|
clusters.push({
|
|
900
1125
|
endpoint: childEndpoint.number.toString(),
|
|
901
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1126
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
902
1127
|
deviceTypes,
|
|
903
1128
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
904
1129
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -911,6 +1136,13 @@ export class Frontend extends EventEmitter {
|
|
|
911
1136
|
});
|
|
912
1137
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
913
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1141
|
+
*
|
|
1142
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1143
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1144
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1145
|
+
*/
|
|
914
1146
|
async wsMessageHandler(client, message) {
|
|
915
1147
|
let data;
|
|
916
1148
|
const sendResponse = (data) => {
|
|
@@ -930,7 +1162,7 @@ export class Frontend extends EventEmitter {
|
|
|
930
1162
|
};
|
|
931
1163
|
try {
|
|
932
1164
|
data = JSON.parse(message.toString());
|
|
933
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1165
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
934
1166
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
935
1167
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
936
1168
|
return;
|
|
@@ -973,35 +1205,48 @@ export class Frontend extends EventEmitter {
|
|
|
973
1205
|
this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
|
|
974
1206
|
const packageName = localData.params.packageName.replace(/@.*$/, '');
|
|
975
1207
|
if (localData.params.restart === false && packageName !== 'matterbridge') {
|
|
1208
|
+
// The install comes from InstallPlugins
|
|
976
1209
|
this.matterbridge.plugins
|
|
977
1210
|
.add(packageName)
|
|
978
1211
|
.then((plugin) => {
|
|
1212
|
+
// istanbul ignore next if
|
|
979
1213
|
if (plugin) {
|
|
1214
|
+
// The plugin is not registered
|
|
980
1215
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
1216
|
+
// In childbridge mode the plugins server node is not started when added
|
|
981
1217
|
if (this.matterbridge.bridgeMode === 'childbridge')
|
|
982
1218
|
this.wssSendRestartRequired(true, true);
|
|
983
1219
|
this.matterbridge.plugins
|
|
984
1220
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1221
|
+
// eslint-disable-next-line promise/no-nesting
|
|
985
1222
|
.then(() => {
|
|
986
1223
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
987
1224
|
this.wssSendRefreshRequired('plugins');
|
|
988
1225
|
return;
|
|
989
1226
|
})
|
|
1227
|
+
// eslint-disable-next-line promise/no-nesting
|
|
990
1228
|
.catch((_error) => {
|
|
1229
|
+
//
|
|
991
1230
|
});
|
|
992
1231
|
}
|
|
993
1232
|
else {
|
|
1233
|
+
// The plugin is already registered
|
|
994
1234
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
995
1235
|
this.wssSendRefreshRequired('plugins');
|
|
996
1236
|
this.wssSendRestartRequired(true, true);
|
|
997
1237
|
}
|
|
998
1238
|
return;
|
|
999
1239
|
})
|
|
1240
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1000
1241
|
.catch((_error) => {
|
|
1242
|
+
//
|
|
1001
1243
|
});
|
|
1002
1244
|
}
|
|
1003
1245
|
else {
|
|
1246
|
+
// The package is matterbridge
|
|
1247
|
+
// istanbul ignore next
|
|
1004
1248
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1249
|
+
// istanbul ignore next if
|
|
1005
1250
|
if (this.matterbridge.restartMode !== '') {
|
|
1006
1251
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
1007
1252
|
this.matterbridge.shutdownProcess();
|
|
@@ -1024,7 +1269,9 @@ export class Frontend extends EventEmitter {
|
|
|
1024
1269
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
|
|
1025
1270
|
return;
|
|
1026
1271
|
}
|
|
1272
|
+
// The package is a plugin
|
|
1027
1273
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1274
|
+
// istanbul ignore next if
|
|
1028
1275
|
if (plugin) {
|
|
1029
1276
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1030
1277
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1032,6 +1279,7 @@ export class Frontend extends EventEmitter {
|
|
|
1032
1279
|
this.wssSendRefreshRequired('plugins');
|
|
1033
1280
|
this.wssSendRefreshRequired('devices');
|
|
1034
1281
|
}
|
|
1282
|
+
// Uninstall the package
|
|
1035
1283
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1036
1284
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1037
1285
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1074,6 +1322,7 @@ export class Frontend extends EventEmitter {
|
|
|
1074
1322
|
return;
|
|
1075
1323
|
})
|
|
1076
1324
|
.catch((_error) => {
|
|
1325
|
+
//
|
|
1077
1326
|
});
|
|
1078
1327
|
}
|
|
1079
1328
|
else {
|
|
@@ -1121,6 +1370,7 @@ export class Frontend extends EventEmitter {
|
|
|
1121
1370
|
return;
|
|
1122
1371
|
})
|
|
1123
1372
|
.catch((_error) => {
|
|
1373
|
+
//
|
|
1124
1374
|
});
|
|
1125
1375
|
}
|
|
1126
1376
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1146,6 +1396,7 @@ export class Frontend extends EventEmitter {
|
|
|
1146
1396
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1147
1397
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1148
1398
|
if (plugin.serverNode) {
|
|
1399
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1149
1400
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1150
1401
|
plugin.serverNode = undefined;
|
|
1151
1402
|
}
|
|
@@ -1155,18 +1406,20 @@ export class Frontend extends EventEmitter {
|
|
|
1155
1406
|
this.matterbridge.devices.remove(device);
|
|
1156
1407
|
}
|
|
1157
1408
|
}
|
|
1409
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1158
1410
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1159
1411
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1160
1412
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1161
|
-
plugin.restartRequired = false;
|
|
1413
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1162
1414
|
let needRestart = 0;
|
|
1163
1415
|
for (const plugin of this.matterbridge.plugins) {
|
|
1164
1416
|
if (plugin.restartRequired)
|
|
1165
1417
|
needRestart++;
|
|
1166
1418
|
}
|
|
1167
1419
|
if (needRestart === 0) {
|
|
1168
|
-
this.wssSendRestartNotRequired(true);
|
|
1420
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1169
1421
|
}
|
|
1422
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1170
1423
|
if (plugin.serverNode)
|
|
1171
1424
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1172
1425
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1422,22 +1675,22 @@ export class Frontend extends EventEmitter {
|
|
|
1422
1675
|
if (isValidString(data.params.value, 4)) {
|
|
1423
1676
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1424
1677
|
if (data.params.value === 'Debug') {
|
|
1425
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1678
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1426
1679
|
}
|
|
1427
1680
|
else if (data.params.value === 'Info') {
|
|
1428
|
-
await this.matterbridge.setLogLevel("info");
|
|
1681
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1429
1682
|
}
|
|
1430
1683
|
else if (data.params.value === 'Notice') {
|
|
1431
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1684
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1432
1685
|
}
|
|
1433
1686
|
else if (data.params.value === 'Warn') {
|
|
1434
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1687
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1435
1688
|
}
|
|
1436
1689
|
else if (data.params.value === 'Error') {
|
|
1437
|
-
await this.matterbridge.setLogLevel("error");
|
|
1690
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1438
1691
|
}
|
|
1439
1692
|
else if (data.params.value === 'Fatal') {
|
|
1440
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1693
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1441
1694
|
}
|
|
1442
1695
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1443
1696
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1448,6 +1701,7 @@ export class Frontend extends EventEmitter {
|
|
|
1448
1701
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1449
1702
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1450
1703
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1704
|
+
// Create the file logger for matterbridge
|
|
1451
1705
|
if (data.params.value)
|
|
1452
1706
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1453
1707
|
else
|
|
@@ -1526,6 +1780,7 @@ export class Frontend extends EventEmitter {
|
|
|
1526
1780
|
}
|
|
1527
1781
|
break;
|
|
1528
1782
|
case 'setmatterport':
|
|
1783
|
+
// eslint-disable-next-line no-case-declarations
|
|
1529
1784
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1530
1785
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1531
1786
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1543,6 +1798,7 @@ export class Frontend extends EventEmitter {
|
|
|
1543
1798
|
}
|
|
1544
1799
|
break;
|
|
1545
1800
|
case 'setmatterdiscriminator':
|
|
1801
|
+
// eslint-disable-next-line no-case-declarations
|
|
1546
1802
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1547
1803
|
if (isValidNumber(discriminator, 1000, 4095)) {
|
|
1548
1804
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1560,6 +1816,7 @@ export class Frontend extends EventEmitter {
|
|
|
1560
1816
|
}
|
|
1561
1817
|
break;
|
|
1562
1818
|
case 'setmatterpasscode':
|
|
1819
|
+
// eslint-disable-next-line no-case-declarations
|
|
1563
1820
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1564
1821
|
if (isValidNumber(passcode, 10000000, 90000000)) {
|
|
1565
1822
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -1603,15 +1860,19 @@ export class Frontend extends EventEmitter {
|
|
|
1603
1860
|
return;
|
|
1604
1861
|
}
|
|
1605
1862
|
const config = plugin.configJson;
|
|
1863
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1606
1864
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1865
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1607
1866
|
if (select === 'serial')
|
|
1608
1867
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1609
1868
|
if (select === 'name')
|
|
1610
1869
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1611
1870
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1871
|
+
// Remove postfix from the serial if it exists
|
|
1612
1872
|
if (config.postfix) {
|
|
1613
1873
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1614
1874
|
}
|
|
1875
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1615
1876
|
if (isValidArray(config.whiteList, 1)) {
|
|
1616
1877
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1617
1878
|
config.whiteList.push(data.params.serial);
|
|
@@ -1620,6 +1881,7 @@ export class Frontend extends EventEmitter {
|
|
|
1620
1881
|
config.whiteList.push(data.params.name);
|
|
1621
1882
|
}
|
|
1622
1883
|
}
|
|
1884
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1623
1885
|
if (isValidArray(config.blackList, 1)) {
|
|
1624
1886
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1625
1887
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1647,7 +1909,9 @@ export class Frontend extends EventEmitter {
|
|
|
1647
1909
|
return;
|
|
1648
1910
|
}
|
|
1649
1911
|
const config = plugin.configJson;
|
|
1912
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1650
1913
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1914
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1651
1915
|
if (select === 'serial')
|
|
1652
1916
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1653
1917
|
if (select === 'name')
|
|
@@ -1656,6 +1920,7 @@ export class Frontend extends EventEmitter {
|
|
|
1656
1920
|
if (config.postfix) {
|
|
1657
1921
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1658
1922
|
}
|
|
1923
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1659
1924
|
if (isValidArray(config.whiteList, 1)) {
|
|
1660
1925
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1661
1926
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1664,6 +1929,7 @@ export class Frontend extends EventEmitter {
|
|
|
1664
1929
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1665
1930
|
}
|
|
1666
1931
|
}
|
|
1932
|
+
// Add the serial to the blackList
|
|
1667
1933
|
if (isValidArray(config.blackList)) {
|
|
1668
1934
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1669
1935
|
config.blackList.push(data.params.serial);
|
|
@@ -1686,6 +1952,7 @@ export class Frontend extends EventEmitter {
|
|
|
1686
1952
|
}
|
|
1687
1953
|
}
|
|
1688
1954
|
else {
|
|
1955
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1689
1956
|
const localData = data;
|
|
1690
1957
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1691
1958
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1695,83 +1962,206 @@ export class Frontend extends EventEmitter {
|
|
|
1695
1962
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1696
1963
|
}
|
|
1697
1964
|
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1967
|
+
*
|
|
1968
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1969
|
+
* @param {string} time - The time string of the message
|
|
1970
|
+
* @param {string} name - The logger name of the message
|
|
1971
|
+
* @param {string} message - The content of the message.
|
|
1972
|
+
*
|
|
1973
|
+
* @remarks
|
|
1974
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1975
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1976
|
+
* The function sends the message to all connected clients.
|
|
1977
|
+
*/
|
|
1698
1978
|
wssSendLogMessage(level, time, name, message) {
|
|
1699
1979
|
if (!level || !time || !name || !message)
|
|
1700
1980
|
return;
|
|
1981
|
+
// Remove ANSI escape codes from the message
|
|
1982
|
+
// eslint-disable-next-line no-control-regex
|
|
1701
1983
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1984
|
+
// Remove leading asterisks from the message
|
|
1702
1985
|
message = message.replace(/^\*+/, '');
|
|
1986
|
+
// Replace all occurrences of \t and \n
|
|
1703
1987
|
message = message.replace(/[\t\n]/g, '');
|
|
1988
|
+
// Remove non-printable characters
|
|
1989
|
+
// eslint-disable-next-line no-control-regex
|
|
1704
1990
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1991
|
+
// Replace all occurrences of \" with "
|
|
1705
1992
|
message = message.replace(/\\"/g, '"');
|
|
1993
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1706
1994
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1995
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1707
1996
|
const maxContinuousLength = 100;
|
|
1708
1997
|
const keepStartLength = 20;
|
|
1709
1998
|
const keepEndLength = 20;
|
|
1999
|
+
// Split the message into words
|
|
1710
2000
|
message = message
|
|
1711
2001
|
.split(' ')
|
|
1712
2002
|
.map((word) => {
|
|
2003
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1713
2004
|
if (word.length > maxContinuousLength) {
|
|
1714
2005
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1715
2006
|
}
|
|
1716
2007
|
return word;
|
|
1717
2008
|
})
|
|
1718
2009
|
.join(' ');
|
|
1719
|
-
|
|
2010
|
+
// Send the message to all connected clients
|
|
2011
|
+
this.wssBroadcastMessage({ id: 0 /* WsBroadcastMessageId.Log */, src: 'Matterbridge', dst: 'Frontend', method: 'log', params: { level, time, name, message } });
|
|
1720
2012
|
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2015
|
+
*
|
|
2016
|
+
* @param {string} changed - The changed value.
|
|
2017
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2018
|
+
* possible values for changed:
|
|
2019
|
+
* - 'settings'
|
|
2020
|
+
* - 'plugins'
|
|
2021
|
+
* - 'devices'
|
|
2022
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2023
|
+
* @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2024
|
+
*/
|
|
1721
2025
|
wssSendRefreshRequired(changed, params) {
|
|
1722
2026
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1723
|
-
|
|
2027
|
+
// Send the message to all connected clients
|
|
2028
|
+
this.wssBroadcastMessage({ id: 1 /* WsBroadcastMessageId.RefreshRequired */, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed, ...params } });
|
|
1724
2029
|
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2032
|
+
*
|
|
2033
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2034
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2035
|
+
*/
|
|
1725
2036
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1726
2037
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1727
2038
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1728
2039
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1729
2040
|
if (snackbar === true)
|
|
1730
2041
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1731
|
-
|
|
2042
|
+
// Send the message to all connected clients
|
|
2043
|
+
this.wssBroadcastMessage({ id: 2 /* WsBroadcastMessageId.RestartRequired */, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } });
|
|
1732
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2047
|
+
*
|
|
2048
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2049
|
+
*/
|
|
1733
2050
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1734
2051
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1735
2052
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1736
2053
|
if (snackbar === true)
|
|
1737
2054
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
1738
|
-
|
|
2055
|
+
// Send the message to all connected clients
|
|
2056
|
+
this.wssBroadcastMessage({ id: 3 /* WsBroadcastMessageId.RestartNotRequired */, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required' });
|
|
1739
2057
|
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2060
|
+
*
|
|
2061
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2062
|
+
*/
|
|
1740
2063
|
wssSendUpdateRequired(devVersion = false) {
|
|
1741
2064
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1742
2065
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1743
|
-
|
|
2066
|
+
// Send the message to all connected clients
|
|
2067
|
+
this.wssBroadcastMessage({ id: 8 /* WsBroadcastMessageId.UpdateRequired */, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required' });
|
|
1744
2068
|
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Sends a cpu update message to all connected clients.
|
|
2071
|
+
*
|
|
2072
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2073
|
+
*/
|
|
1745
2074
|
wssSendCpuUpdate(cpuUsage) {
|
|
1746
2075
|
if (hasParameter('debug'))
|
|
1747
2076
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1748
|
-
|
|
2077
|
+
// Send the message to all connected clients
|
|
2078
|
+
this.wssBroadcastMessage({ id: 4 /* WsBroadcastMessageId.CpuUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
|
|
1749
2079
|
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Sends a memory update message to all connected clients.
|
|
2082
|
+
*
|
|
2083
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2084
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2085
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2086
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2087
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2088
|
+
* @param {string} external - The external memory in bytes.
|
|
2089
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2090
|
+
*/
|
|
1750
2091
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1751
2092
|
if (hasParameter('debug'))
|
|
1752
2093
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1753
|
-
|
|
2094
|
+
// Send the message to all connected clients
|
|
2095
|
+
this.wssBroadcastMessage({ id: 5 /* WsBroadcastMessageId.MemoryUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1754
2096
|
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Sends an uptime update message to all connected clients.
|
|
2099
|
+
*
|
|
2100
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2101
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2102
|
+
*/
|
|
1755
2103
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1756
2104
|
if (hasParameter('debug'))
|
|
1757
2105
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1758
|
-
|
|
2106
|
+
// Send the message to all connected clients
|
|
2107
|
+
this.wssBroadcastMessage({ id: 6 /* WsBroadcastMessageId.UptimeUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } });
|
|
1759
2108
|
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Sends an open snackbar message to all connected clients.
|
|
2111
|
+
*
|
|
2112
|
+
* @param {string} message - The message to send.
|
|
2113
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2114
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2115
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2116
|
+
*
|
|
2117
|
+
* @remarks
|
|
2118
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2119
|
+
*/
|
|
1760
2120
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1761
2121
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1762
|
-
|
|
2122
|
+
// Send the message to all connected clients
|
|
2123
|
+
this.wssBroadcastMessage({ id: 7 /* WsBroadcastMessageId.Snackbar */, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', params: { message, timeout, severity } });
|
|
1763
2124
|
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Sends a close snackbar message to all connected clients.
|
|
2127
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2128
|
+
*
|
|
2129
|
+
* @param {string} message - The message to send.
|
|
2130
|
+
*/
|
|
1764
2131
|
wssSendCloseSnackbarMessage(message) {
|
|
1765
2132
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
1766
|
-
|
|
2133
|
+
// Send the message to all connected clients
|
|
2134
|
+
this.wssBroadcastMessage({ id: 10 /* WsBroadcastMessageId.CloseSnackbar */, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', params: { message } });
|
|
1767
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2138
|
+
*
|
|
2139
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2140
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2141
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2142
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2143
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2144
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2145
|
+
*
|
|
2146
|
+
* @remarks
|
|
2147
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2148
|
+
* with the updated attribute information.
|
|
2149
|
+
*/
|
|
1768
2150
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1769
2151
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
1770
|
-
|
|
2152
|
+
// Send the message to all connected clients
|
|
2153
|
+
this.wssBroadcastMessage({ id: 9 /* WsBroadcastMessageId.StateUpdate */, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } });
|
|
1771
2154
|
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Sends a message to all connected clients.
|
|
2157
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2158
|
+
*
|
|
2159
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2160
|
+
*/
|
|
1772
2161
|
wssBroadcastMessage(msg) {
|
|
2162
|
+
// Send the message to all connected clients
|
|
1773
2163
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1774
|
-
if (msg.id !== 0)
|
|
2164
|
+
if (msg.id !== 0 /* WsBroadcastMessageId.Log */)
|
|
1775
2165
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
1776
2166
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1777
2167
|
if (client.readyState === WebSocket.OPEN) {
|
|
@@ -1780,3 +2170,4 @@ export class Frontend extends EventEmitter {
|
|
|
1780
2170
|
});
|
|
1781
2171
|
}
|
|
1782
2172
|
}
|
|
2173
|
+
//# sourceMappingURL=frontend.js.map
|