matterbridge 3.2.9-dev-20250926-85736bb → 3.2.9
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 +2 -2
- 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 +232 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +428 -34
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +514 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- 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 +444 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +784 -51
- 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 +1534 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1398 -58
- 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 +365 -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 +197 -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 +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +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 +34 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +84 -6
- 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 +7 -5
- package/npm-shrinkwrap.json +22 -16
- 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,14 +29,18 @@ 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';
|
|
15
42
|
import { CommissioningOptions } from '@matter/main/types';
|
|
43
|
+
// Matterbridge
|
|
16
44
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
|
|
17
45
|
import { plg } from './matterbridgeTypes.js';
|
|
18
46
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
@@ -29,7 +57,7 @@ export class Frontend extends EventEmitter {
|
|
|
29
57
|
constructor(matterbridge) {
|
|
30
58
|
super();
|
|
31
59
|
this.matterbridge = matterbridge;
|
|
32
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
60
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
33
61
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
34
62
|
}
|
|
35
63
|
set logLevel(logLevel) {
|
|
@@ -38,10 +66,39 @@ export class Frontend extends EventEmitter {
|
|
|
38
66
|
async start(port = 8283) {
|
|
39
67
|
this.port = port;
|
|
40
68
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
41
|
-
|
|
69
|
+
// Initialize multer with the upload directory
|
|
70
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
42
71
|
const upload = multer({ dest: uploadDir });
|
|
72
|
+
// Create the express app that serves the frontend
|
|
43
73
|
this.expressApp = express();
|
|
74
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
75
|
+
/*
|
|
76
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
77
|
+
for (const method of methods) {
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
82
|
+
try {
|
|
83
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
84
|
+
return original(path, ...rest);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
*/
|
|
92
|
+
// Log all requests to the server for debugging
|
|
93
|
+
/*
|
|
94
|
+
this.expressApp.use((req, res, next) => {
|
|
95
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
96
|
+
next();
|
|
97
|
+
});
|
|
98
|
+
*/
|
|
99
|
+
// Serve static files from '/static' endpoint
|
|
44
100
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
101
|
+
// Read the package.json file to get the frontend version
|
|
45
102
|
try {
|
|
46
103
|
this.log.debug(`Reading frontend package.json...`);
|
|
47
104
|
const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
|
|
@@ -49,9 +106,11 @@ export class Frontend extends EventEmitter {
|
|
|
49
106
|
this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
|
|
50
107
|
}
|
|
51
108
|
catch (error) {
|
|
109
|
+
// istanbul ignore next
|
|
52
110
|
this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
111
|
}
|
|
54
112
|
if (!hasParameter('ssl')) {
|
|
113
|
+
// Create an HTTP server and attach the express app
|
|
55
114
|
try {
|
|
56
115
|
this.log.debug(`Creating HTTP server...`);
|
|
57
116
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -61,6 +120,7 @@ export class Frontend extends EventEmitter {
|
|
|
61
120
|
this.emit('server_error', error);
|
|
62
121
|
return;
|
|
63
122
|
}
|
|
123
|
+
// Listen on the specified port
|
|
64
124
|
if (hasParameter('ingress')) {
|
|
65
125
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
66
126
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -101,6 +161,7 @@ export class Frontend extends EventEmitter {
|
|
|
101
161
|
let passphrase;
|
|
102
162
|
let httpsServerOptions = {};
|
|
103
163
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
164
|
+
// Load the p12 certificate and the passphrase
|
|
104
165
|
try {
|
|
105
166
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
106
167
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -112,7 +173,7 @@ export class Frontend extends EventEmitter {
|
|
|
112
173
|
}
|
|
113
174
|
try {
|
|
114
175
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
115
|
-
passphrase = passphrase.trim();
|
|
176
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
116
177
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
117
178
|
}
|
|
118
179
|
catch (error) {
|
|
@@ -123,6 +184,7 @@ export class Frontend extends EventEmitter {
|
|
|
123
184
|
httpsServerOptions = { pfx, passphrase };
|
|
124
185
|
}
|
|
125
186
|
else {
|
|
187
|
+
// 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.
|
|
126
188
|
try {
|
|
127
189
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
128
190
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -152,9 +214,10 @@ export class Frontend extends EventEmitter {
|
|
|
152
214
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
153
215
|
}
|
|
154
216
|
if (hasParameter('mtls')) {
|
|
155
|
-
httpsServerOptions.requestCert = true;
|
|
156
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
217
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
218
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
157
219
|
}
|
|
220
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
158
221
|
try {
|
|
159
222
|
this.log.debug(`Creating HTTPS server...`);
|
|
160
223
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -164,6 +227,7 @@ export class Frontend extends EventEmitter {
|
|
|
164
227
|
this.emit('server_error', error);
|
|
165
228
|
return;
|
|
166
229
|
}
|
|
230
|
+
// Listen on the specified port
|
|
167
231
|
if (hasParameter('ingress')) {
|
|
168
232
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
169
233
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -195,17 +259,19 @@ export class Frontend extends EventEmitter {
|
|
|
195
259
|
return;
|
|
196
260
|
});
|
|
197
261
|
}
|
|
262
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
198
263
|
const wssPort = this.port;
|
|
199
264
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
200
265
|
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
201
266
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
202
267
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
203
268
|
const clientIp = request.socket.remoteAddress;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
269
|
+
// Set the global logger callback for the WebSocketServer
|
|
270
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
271
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
272
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
273
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
274
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
209
275
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
210
276
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
211
277
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -227,6 +293,7 @@ export class Frontend extends EventEmitter {
|
|
|
227
293
|
}
|
|
228
294
|
});
|
|
229
295
|
ws.on('error', (error) => {
|
|
296
|
+
// istanbul ignore next
|
|
230
297
|
this.log.error(`WebSocket client error: ${error}`);
|
|
231
298
|
});
|
|
232
299
|
});
|
|
@@ -240,6 +307,7 @@ export class Frontend extends EventEmitter {
|
|
|
240
307
|
this.webSocketServer.on('error', (ws, error) => {
|
|
241
308
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
242
309
|
});
|
|
310
|
+
// Subscribe to cli events
|
|
243
311
|
cliEmitter.removeAllListeners();
|
|
244
312
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
245
313
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -250,6 +318,8 @@ export class Frontend extends EventEmitter {
|
|
|
250
318
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
251
319
|
this.wssSendCpuUpdate(cpuUsage);
|
|
252
320
|
});
|
|
321
|
+
// Endpoint to validate login code
|
|
322
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
253
323
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
254
324
|
const { password } = req.body;
|
|
255
325
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -268,23 +338,27 @@ export class Frontend extends EventEmitter {
|
|
|
268
338
|
this.log.warn('/api/login error wrong password');
|
|
269
339
|
res.json({ valid: false });
|
|
270
340
|
}
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
271
342
|
}
|
|
272
343
|
catch (error) {
|
|
273
344
|
this.log.error('/api/login error getting password');
|
|
274
345
|
res.json({ valid: false });
|
|
275
346
|
}
|
|
276
347
|
});
|
|
348
|
+
// Endpoint to provide health check for docker
|
|
277
349
|
this.expressApp.get('/health', (req, res) => {
|
|
278
350
|
this.log.debug('Express received /health');
|
|
279
351
|
const healthStatus = {
|
|
280
|
-
status: 'ok',
|
|
281
|
-
uptime: process.uptime(),
|
|
282
|
-
timestamp: new Date().toISOString(),
|
|
352
|
+
status: 'ok', // Indicate service is healthy
|
|
353
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
354
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
283
355
|
};
|
|
284
356
|
res.status(200).json(healthStatus);
|
|
285
357
|
});
|
|
358
|
+
// Endpoint to provide memory usage details
|
|
286
359
|
this.expressApp.get('/memory', async (req, res) => {
|
|
287
360
|
this.log.debug('Express received /memory');
|
|
361
|
+
// Memory usage from process
|
|
288
362
|
const memoryUsageRaw = process.memoryUsage();
|
|
289
363
|
const memoryUsage = {
|
|
290
364
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -293,10 +367,13 @@ export class Frontend extends EventEmitter {
|
|
|
293
367
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
294
368
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
295
369
|
};
|
|
370
|
+
// V8 heap statistics
|
|
296
371
|
const { default: v8 } = await import('node:v8');
|
|
297
372
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
298
373
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
374
|
+
// Format heapStats
|
|
299
375
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
376
|
+
// Format heapSpaces
|
|
300
377
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
301
378
|
...space,
|
|
302
379
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -314,19 +391,23 @@ export class Frontend extends EventEmitter {
|
|
|
314
391
|
};
|
|
315
392
|
res.status(200).json(memoryReport);
|
|
316
393
|
});
|
|
394
|
+
// Endpoint to provide settings
|
|
317
395
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
318
396
|
this.log.debug('The frontend sent /api/settings');
|
|
319
397
|
res.json(await this.getApiSettings());
|
|
320
398
|
});
|
|
399
|
+
// Endpoint to provide plugins
|
|
321
400
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
322
401
|
this.log.debug('The frontend sent /api/plugins');
|
|
323
402
|
res.json(this.getPlugins());
|
|
324
403
|
});
|
|
404
|
+
// Endpoint to provide devices
|
|
325
405
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
326
406
|
this.log.debug('The frontend sent /api/devices');
|
|
327
407
|
const devices = await this.getDevices();
|
|
328
408
|
res.json(devices);
|
|
329
409
|
});
|
|
410
|
+
// Endpoint to view the matterbridge log
|
|
330
411
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
331
412
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
332
413
|
try {
|
|
@@ -339,6 +420,7 @@ export class Frontend extends EventEmitter {
|
|
|
339
420
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
340
421
|
}
|
|
341
422
|
});
|
|
423
|
+
// Endpoint to view the matter.js log
|
|
342
424
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
343
425
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
344
426
|
try {
|
|
@@ -351,9 +433,11 @@ export class Frontend extends EventEmitter {
|
|
|
351
433
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
352
434
|
}
|
|
353
435
|
});
|
|
436
|
+
// Endpoint to view the diagnostic.log
|
|
354
437
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
355
438
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
356
439
|
const serverNodes = [];
|
|
440
|
+
// istanbul ignore else
|
|
357
441
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
358
442
|
if (this.matterbridge.serverNode)
|
|
359
443
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -364,6 +448,7 @@ export class Frontend extends EventEmitter {
|
|
|
364
448
|
serverNodes.push(plugin.serverNode);
|
|
365
449
|
}
|
|
366
450
|
}
|
|
451
|
+
// istanbul ignore next
|
|
367
452
|
for (const device of this.matterbridge.getDevices()) {
|
|
368
453
|
if (device.serverNode)
|
|
369
454
|
serverNodes.push(device.serverNode);
|
|
@@ -386,17 +471,20 @@ export class Frontend extends EventEmitter {
|
|
|
386
471
|
values: [...serverNodes],
|
|
387
472
|
})));
|
|
388
473
|
delete Logger.destinations.diagnostic;
|
|
389
|
-
await wait(500);
|
|
474
|
+
await wait(500); // Wait for the log to be written
|
|
390
475
|
try {
|
|
391
476
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
|
|
392
477
|
res.type('text/plain');
|
|
393
478
|
res.send(data.slice(29));
|
|
394
479
|
}
|
|
395
480
|
catch (error) {
|
|
481
|
+
// istanbul ignore next
|
|
396
482
|
this.log.error(`Error reading diagnostic log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
483
|
+
// istanbul ignore next
|
|
397
484
|
res.status(500).send('Error reading diagnostic log file.');
|
|
398
485
|
}
|
|
399
486
|
});
|
|
487
|
+
// Endpoint to view the shelly log
|
|
400
488
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
401
489
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
402
490
|
try {
|
|
@@ -409,6 +497,7 @@ export class Frontend extends EventEmitter {
|
|
|
409
497
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
410
498
|
}
|
|
411
499
|
});
|
|
500
|
+
// Endpoint to download the matterbridge log
|
|
412
501
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
413
502
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
414
503
|
try {
|
|
@@ -422,12 +511,14 @@ export class Frontend extends EventEmitter {
|
|
|
422
511
|
}
|
|
423
512
|
res.type('text/plain');
|
|
424
513
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
514
|
+
/* istanbul ignore if */
|
|
425
515
|
if (error) {
|
|
426
516
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
427
517
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
428
518
|
}
|
|
429
519
|
});
|
|
430
520
|
});
|
|
521
|
+
// Endpoint to download the matter log
|
|
431
522
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
432
523
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
433
524
|
try {
|
|
@@ -441,12 +532,14 @@ export class Frontend extends EventEmitter {
|
|
|
441
532
|
}
|
|
442
533
|
res.type('text/plain');
|
|
443
534
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
535
|
+
/* istanbul ignore if */
|
|
444
536
|
if (error) {
|
|
445
537
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
446
538
|
res.status(500).send('Error downloading the matter log file');
|
|
447
539
|
}
|
|
448
540
|
});
|
|
449
541
|
});
|
|
542
|
+
// Endpoint to download the shelly log
|
|
450
543
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
451
544
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
452
545
|
try {
|
|
@@ -460,74 +553,90 @@ export class Frontend extends EventEmitter {
|
|
|
460
553
|
}
|
|
461
554
|
res.type('text/plain');
|
|
462
555
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
556
|
+
/* istanbul ignore if */
|
|
463
557
|
if (error) {
|
|
464
558
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
465
559
|
res.status(500).send('Error downloading Shelly system log file');
|
|
466
560
|
}
|
|
467
561
|
});
|
|
468
562
|
});
|
|
563
|
+
// Endpoint to download the matterbridge storage directory
|
|
469
564
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
470
565
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
471
566
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
472
567
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
568
|
+
/* istanbul ignore if */
|
|
473
569
|
if (error) {
|
|
474
570
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
475
571
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
476
572
|
}
|
|
477
573
|
});
|
|
478
574
|
});
|
|
575
|
+
// Endpoint to download the matter storage file
|
|
479
576
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
480
577
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
481
578
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
482
579
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
580
|
+
/* istanbul ignore if */
|
|
483
581
|
if (error) {
|
|
484
582
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
485
583
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
486
584
|
}
|
|
487
585
|
});
|
|
488
586
|
});
|
|
587
|
+
// Endpoint to download the matterbridge plugin directory
|
|
489
588
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
490
589
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
491
590
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
492
591
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
592
|
+
/* istanbul ignore if */
|
|
493
593
|
if (error) {
|
|
494
594
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
495
595
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
496
596
|
}
|
|
497
597
|
});
|
|
498
598
|
});
|
|
599
|
+
// Endpoint to download the matterbridge plugin config files
|
|
499
600
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
500
601
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
501
602
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
502
603
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
604
|
+
/* istanbul ignore if */
|
|
503
605
|
if (error) {
|
|
504
606
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
505
607
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
506
608
|
}
|
|
507
609
|
});
|
|
508
610
|
});
|
|
611
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
509
612
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
510
613
|
this.log.debug('The frontend sent /api/download-backup');
|
|
511
614
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
615
|
+
/* istanbul ignore if */
|
|
512
616
|
if (error) {
|
|
513
617
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
514
618
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
515
619
|
}
|
|
516
620
|
});
|
|
517
621
|
});
|
|
622
|
+
// Endpoint to upload a package
|
|
518
623
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
519
624
|
const { filename } = req.body;
|
|
520
625
|
const file = req.file;
|
|
626
|
+
/* istanbul ignore if */
|
|
521
627
|
if (!file || !filename) {
|
|
522
628
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
523
629
|
res.status(400).send('Invalid request: file and filename are required');
|
|
524
630
|
return;
|
|
525
631
|
}
|
|
526
632
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
633
|
+
// Define the path where the plugin file will be saved
|
|
527
634
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
528
635
|
try {
|
|
636
|
+
// Move the uploaded file to the specified path
|
|
529
637
|
await fs.rename(file.path, filePath);
|
|
530
638
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
639
|
+
// Install the plugin package
|
|
531
640
|
if (filename.endsWith('.tgz')) {
|
|
532
641
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
533
642
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
|
|
@@ -547,6 +656,7 @@ export class Frontend extends EventEmitter {
|
|
|
547
656
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
548
657
|
}
|
|
549
658
|
});
|
|
659
|
+
// Fallback for routing (must be the last route)
|
|
550
660
|
this.expressApp.use((req, res) => {
|
|
551
661
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
552
662
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -555,13 +665,16 @@ export class Frontend extends EventEmitter {
|
|
|
555
665
|
}
|
|
556
666
|
async stop() {
|
|
557
667
|
this.log.debug('Stopping the frontend...');
|
|
668
|
+
// Remove listeners from the express app
|
|
558
669
|
if (this.expressApp) {
|
|
559
670
|
this.expressApp.removeAllListeners();
|
|
560
671
|
this.expressApp = undefined;
|
|
561
672
|
this.log.debug('Frontend app closed successfully');
|
|
562
673
|
}
|
|
674
|
+
// Close the WebSocket server
|
|
563
675
|
if (this.webSocketServer) {
|
|
564
676
|
this.log.debug('Closing WebSocket server...');
|
|
677
|
+
// Close all active connections
|
|
565
678
|
this.webSocketServer.clients.forEach((client) => {
|
|
566
679
|
if (client.readyState === WebSocket.OPEN) {
|
|
567
680
|
client.close();
|
|
@@ -570,6 +683,7 @@ export class Frontend extends EventEmitter {
|
|
|
570
683
|
await withTimeout(new Promise((resolve) => {
|
|
571
684
|
this.webSocketServer?.close((error) => {
|
|
572
685
|
if (error) {
|
|
686
|
+
// istanbul ignore next
|
|
573
687
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
574
688
|
}
|
|
575
689
|
else {
|
|
@@ -582,8 +696,27 @@ export class Frontend extends EventEmitter {
|
|
|
582
696
|
this.webSocketServer.removeAllListeners();
|
|
583
697
|
this.webSocketServer = undefined;
|
|
584
698
|
}
|
|
699
|
+
// Close the http server
|
|
585
700
|
if (this.httpServer) {
|
|
586
701
|
this.log.debug('Closing http server...');
|
|
702
|
+
/*
|
|
703
|
+
await withTimeout(
|
|
704
|
+
new Promise<void>((resolve) => {
|
|
705
|
+
this.httpServer?.close((error) => {
|
|
706
|
+
if (error) {
|
|
707
|
+
// istanbul ignore next
|
|
708
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
709
|
+
} else {
|
|
710
|
+
this.log.debug('Http server closed successfully');
|
|
711
|
+
this.emit('server_stopped');
|
|
712
|
+
}
|
|
713
|
+
resolve();
|
|
714
|
+
});
|
|
715
|
+
}),
|
|
716
|
+
5000,
|
|
717
|
+
false,
|
|
718
|
+
);
|
|
719
|
+
*/
|
|
587
720
|
this.httpServer.close();
|
|
588
721
|
this.log.debug('Http server closed successfully');
|
|
589
722
|
this.listening = false;
|
|
@@ -592,8 +725,27 @@ export class Frontend extends EventEmitter {
|
|
|
592
725
|
this.httpServer = undefined;
|
|
593
726
|
this.log.debug('Frontend http server closed successfully');
|
|
594
727
|
}
|
|
728
|
+
// Close the https server
|
|
595
729
|
if (this.httpsServer) {
|
|
596
730
|
this.log.debug('Closing https server...');
|
|
731
|
+
/*
|
|
732
|
+
await withTimeout(
|
|
733
|
+
new Promise<void>((resolve) => {
|
|
734
|
+
this.httpsServer?.close((error) => {
|
|
735
|
+
if (error) {
|
|
736
|
+
// istanbul ignore next
|
|
737
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
738
|
+
} else {
|
|
739
|
+
this.log.debug('Https server closed successfully');
|
|
740
|
+
this.emit('server_stopped');
|
|
741
|
+
}
|
|
742
|
+
resolve();
|
|
743
|
+
});
|
|
744
|
+
}),
|
|
745
|
+
5000,
|
|
746
|
+
false,
|
|
747
|
+
);
|
|
748
|
+
*/
|
|
597
749
|
this.httpsServer.close();
|
|
598
750
|
this.log.debug('Https server closed successfully');
|
|
599
751
|
this.listening = false;
|
|
@@ -604,6 +756,7 @@ export class Frontend extends EventEmitter {
|
|
|
604
756
|
}
|
|
605
757
|
this.log.debug('Frontend stopped successfully');
|
|
606
758
|
}
|
|
759
|
+
// Function to format bytes to KB, MB, or GB
|
|
607
760
|
formatMemoryUsage = (bytes) => {
|
|
608
761
|
if (bytes >= 1024 ** 3) {
|
|
609
762
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -615,6 +768,7 @@ export class Frontend extends EventEmitter {
|
|
|
615
768
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
616
769
|
}
|
|
617
770
|
};
|
|
771
|
+
// Function to format system uptime with only the most significant unit
|
|
618
772
|
formatOsUpTime = (seconds) => {
|
|
619
773
|
if (seconds >= 86400) {
|
|
620
774
|
const days = Math.floor(seconds / 86400);
|
|
@@ -630,7 +784,13 @@ export class Frontend extends EventEmitter {
|
|
|
630
784
|
}
|
|
631
785
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
632
786
|
};
|
|
787
|
+
/**
|
|
788
|
+
* Retrieves the api settings data.
|
|
789
|
+
*
|
|
790
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
791
|
+
*/
|
|
633
792
|
async getApiSettings() {
|
|
793
|
+
// Update the system information
|
|
634
794
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
635
795
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
636
796
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -639,6 +799,7 @@ export class Frontend extends EventEmitter {
|
|
|
639
799
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
640
800
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
641
801
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
802
|
+
// Update the matterbridge information
|
|
642
803
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
643
804
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
644
805
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -652,6 +813,12 @@ export class Frontend extends EventEmitter {
|
|
|
652
813
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
653
814
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
654
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Retrieves the reachable attribute.
|
|
818
|
+
*
|
|
819
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
820
|
+
* @returns {boolean} The reachable attribute.
|
|
821
|
+
*/
|
|
655
822
|
getReachability(device) {
|
|
656
823
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
657
824
|
return false;
|
|
@@ -663,6 +830,12 @@ export class Frontend extends EventEmitter {
|
|
|
663
830
|
return true;
|
|
664
831
|
return false;
|
|
665
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Retrieves the power source attribute.
|
|
835
|
+
*
|
|
836
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
837
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
838
|
+
*/
|
|
666
839
|
getPowerSource(endpoint) {
|
|
667
840
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
668
841
|
return undefined;
|
|
@@ -678,13 +851,22 @@ export class Frontend extends EventEmitter {
|
|
|
678
851
|
}
|
|
679
852
|
return;
|
|
680
853
|
};
|
|
854
|
+
// Root endpoint
|
|
681
855
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
682
856
|
return powerSource(endpoint);
|
|
857
|
+
// Child endpoints
|
|
683
858
|
for (const child of endpoint.getChildEndpoints()) {
|
|
684
859
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
685
860
|
return powerSource(child);
|
|
686
861
|
}
|
|
687
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Retrieves the cluster text description from a given device.
|
|
865
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
866
|
+
*
|
|
867
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
868
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
869
|
+
*/
|
|
688
870
|
getClusterTextFromDevice(device) {
|
|
689
871
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
690
872
|
return '';
|
|
@@ -695,6 +877,7 @@ export class Frontend extends EventEmitter {
|
|
|
695
877
|
if (composed)
|
|
696
878
|
return 'Composed: ' + composed.value;
|
|
697
879
|
}
|
|
880
|
+
// istanbul ignore next cause is not reachable
|
|
698
881
|
return '';
|
|
699
882
|
};
|
|
700
883
|
const getFixedLabel = (device) => {
|
|
@@ -704,11 +887,13 @@ export class Frontend extends EventEmitter {
|
|
|
704
887
|
if (composed)
|
|
705
888
|
return 'Composed: ' + composed.value;
|
|
706
889
|
}
|
|
890
|
+
// istanbul ignore next cause is not reacheable
|
|
707
891
|
return '';
|
|
708
892
|
};
|
|
709
893
|
let attributes = '';
|
|
710
894
|
let supportedModes = [];
|
|
711
895
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
896
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
712
897
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
713
898
|
return;
|
|
714
899
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -798,11 +983,17 @@ export class Frontend extends EventEmitter {
|
|
|
798
983
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
799
984
|
attributes += `${getUserLabel(device)} `;
|
|
800
985
|
});
|
|
986
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
801
987
|
return attributes.trimStart().trimEnd();
|
|
802
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
991
|
+
*
|
|
992
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
993
|
+
*/
|
|
803
994
|
getPlugins() {
|
|
804
995
|
if (this.matterbridge.hasCleanupStarted)
|
|
805
|
-
return [];
|
|
996
|
+
return []; // Skip if cleanup has started
|
|
806
997
|
const baseRegisteredPlugins = [];
|
|
807
998
|
for (const plugin of this.matterbridge.plugins) {
|
|
808
999
|
baseRegisteredPlugins.push({
|
|
@@ -830,18 +1021,27 @@ export class Frontend extends EventEmitter {
|
|
|
830
1021
|
schemaJson: plugin.schemaJson,
|
|
831
1022
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
832
1023
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1024
|
+
// Childbridge mode specific data
|
|
833
1025
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
834
1026
|
});
|
|
835
1027
|
}
|
|
836
1028
|
return baseRegisteredPlugins;
|
|
837
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Retrieves the devices from Matterbridge.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1034
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1035
|
+
*/
|
|
838
1036
|
async getDevices(pluginName) {
|
|
839
1037
|
if (this.matterbridge.hasCleanupStarted)
|
|
840
|
-
return [];
|
|
1038
|
+
return []; // Skip if cleanup has started
|
|
841
1039
|
const devices = [];
|
|
842
1040
|
for (const device of this.matterbridge.devices.array()) {
|
|
1041
|
+
// Filter by pluginName if provided
|
|
843
1042
|
if (pluginName && pluginName !== device.plugin)
|
|
844
1043
|
continue;
|
|
1044
|
+
// Check if the device has the required properties
|
|
845
1045
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
846
1046
|
continue;
|
|
847
1047
|
devices.push({
|
|
@@ -861,22 +1061,37 @@ export class Frontend extends EventEmitter {
|
|
|
861
1061
|
}
|
|
862
1062
|
return devices;
|
|
863
1063
|
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1066
|
+
*
|
|
1067
|
+
* Response for /api/clusters
|
|
1068
|
+
*
|
|
1069
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1070
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1071
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1072
|
+
*/
|
|
864
1073
|
getClusters(pluginName, endpointNumber) {
|
|
865
1074
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
866
1075
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
867
1076
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
868
1077
|
return;
|
|
869
1078
|
}
|
|
1079
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1080
|
+
// Get the device types from the main endpoint
|
|
870
1081
|
const deviceTypes = [];
|
|
871
1082
|
const clusters = [];
|
|
872
1083
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
873
1084
|
deviceTypes.push(d.deviceType);
|
|
874
1085
|
});
|
|
1086
|
+
// Get the clusters from the main endpoint
|
|
875
1087
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
876
1088
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
877
1089
|
return;
|
|
878
1090
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
879
1091
|
return;
|
|
1092
|
+
// console.log(
|
|
1093
|
+
// `${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}`,
|
|
1094
|
+
// );
|
|
880
1095
|
clusters.push({
|
|
881
1096
|
endpoint: endpoint.number.toString(),
|
|
882
1097
|
number: endpoint.number,
|
|
@@ -890,12 +1105,19 @@ export class Frontend extends EventEmitter {
|
|
|
890
1105
|
attributeLocalValue: attributeValue,
|
|
891
1106
|
});
|
|
892
1107
|
});
|
|
1108
|
+
// Get the child endpoints
|
|
893
1109
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1110
|
+
// if (childEndpoints.length === 0) {
|
|
1111
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1112
|
+
// }
|
|
894
1113
|
childEndpoints.forEach((childEndpoint) => {
|
|
1114
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
895
1115
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
896
1116
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
897
1117
|
return;
|
|
898
1118
|
}
|
|
1119
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1120
|
+
// Get the device types of the child endpoint
|
|
899
1121
|
const deviceTypes = [];
|
|
900
1122
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
901
1123
|
deviceTypes.push(d.deviceType);
|
|
@@ -905,6 +1127,9 @@ export class Frontend extends EventEmitter {
|
|
|
905
1127
|
return;
|
|
906
1128
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
907
1129
|
return;
|
|
1130
|
+
// console.log(
|
|
1131
|
+
// `${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}`,
|
|
1132
|
+
// );
|
|
908
1133
|
clusters.push({
|
|
909
1134
|
endpoint: childEndpoint.number.toString(),
|
|
910
1135
|
number: childEndpoint.number,
|
|
@@ -921,6 +1146,13 @@ export class Frontend extends EventEmitter {
|
|
|
921
1146
|
});
|
|
922
1147
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
|
|
923
1148
|
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1151
|
+
*
|
|
1152
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1153
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1154
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1155
|
+
*/
|
|
924
1156
|
async wsMessageHandler(client, message) {
|
|
925
1157
|
let data;
|
|
926
1158
|
const sendResponse = (data) => {
|
|
@@ -940,7 +1172,7 @@ export class Frontend extends EventEmitter {
|
|
|
940
1172
|
};
|
|
941
1173
|
try {
|
|
942
1174
|
data = JSON.parse(message.toString());
|
|
943
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1175
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
944
1176
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
945
1177
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
946
1178
|
return;
|
|
@@ -983,35 +1215,48 @@ export class Frontend extends EventEmitter {
|
|
|
983
1215
|
this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
|
|
984
1216
|
const packageName = localData.params.packageName.replace(/@.*$/, '');
|
|
985
1217
|
if (localData.params.restart === false && packageName !== 'matterbridge') {
|
|
1218
|
+
// The install comes from InstallPlugins
|
|
986
1219
|
this.matterbridge.plugins
|
|
987
1220
|
.add(packageName)
|
|
988
1221
|
.then((plugin) => {
|
|
1222
|
+
// istanbul ignore next if
|
|
989
1223
|
if (plugin) {
|
|
1224
|
+
// The plugin is not registered
|
|
990
1225
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
1226
|
+
// In childbridge mode the plugins server node is not started when added
|
|
991
1227
|
if (this.matterbridge.bridgeMode === 'childbridge')
|
|
992
1228
|
this.wssSendRestartRequired(true, true);
|
|
993
1229
|
this.matterbridge.plugins
|
|
994
1230
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1231
|
+
// eslint-disable-next-line promise/no-nesting
|
|
995
1232
|
.then(() => {
|
|
996
1233
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
997
1234
|
this.wssSendRefreshRequired('plugins');
|
|
998
1235
|
return;
|
|
999
1236
|
})
|
|
1237
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1000
1238
|
.catch((_error) => {
|
|
1239
|
+
//
|
|
1001
1240
|
});
|
|
1002
1241
|
}
|
|
1003
1242
|
else {
|
|
1243
|
+
// The plugin is already registered
|
|
1004
1244
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1005
1245
|
this.wssSendRefreshRequired('plugins');
|
|
1006
1246
|
this.wssSendRestartRequired(true, true);
|
|
1007
1247
|
}
|
|
1008
1248
|
return;
|
|
1009
1249
|
})
|
|
1250
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1010
1251
|
.catch((_error) => {
|
|
1252
|
+
//
|
|
1011
1253
|
});
|
|
1012
1254
|
}
|
|
1013
1255
|
else {
|
|
1256
|
+
// The package is matterbridge
|
|
1257
|
+
// istanbul ignore next
|
|
1014
1258
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1259
|
+
// istanbul ignore next if
|
|
1015
1260
|
if (this.matterbridge.restartMode !== '') {
|
|
1016
1261
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
1017
1262
|
this.matterbridge.shutdownProcess();
|
|
@@ -1034,7 +1279,9 @@ export class Frontend extends EventEmitter {
|
|
|
1034
1279
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
|
|
1035
1280
|
return;
|
|
1036
1281
|
}
|
|
1282
|
+
// The package is a plugin
|
|
1037
1283
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1284
|
+
// istanbul ignore next if
|
|
1038
1285
|
if (plugin) {
|
|
1039
1286
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1040
1287
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1042,6 +1289,7 @@ export class Frontend extends EventEmitter {
|
|
|
1042
1289
|
this.wssSendRefreshRequired('plugins');
|
|
1043
1290
|
this.wssSendRefreshRequired('devices');
|
|
1044
1291
|
}
|
|
1292
|
+
// Uninstall the package
|
|
1045
1293
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1046
1294
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1047
1295
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
|
|
@@ -1084,6 +1332,7 @@ export class Frontend extends EventEmitter {
|
|
|
1084
1332
|
return;
|
|
1085
1333
|
})
|
|
1086
1334
|
.catch((_error) => {
|
|
1335
|
+
//
|
|
1087
1336
|
});
|
|
1088
1337
|
}
|
|
1089
1338
|
else {
|
|
@@ -1131,6 +1380,7 @@ export class Frontend extends EventEmitter {
|
|
|
1131
1380
|
return;
|
|
1132
1381
|
})
|
|
1133
1382
|
.catch((_error) => {
|
|
1383
|
+
//
|
|
1134
1384
|
});
|
|
1135
1385
|
}
|
|
1136
1386
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1156,6 +1406,7 @@ export class Frontend extends EventEmitter {
|
|
|
1156
1406
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1157
1407
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1158
1408
|
if (plugin.serverNode) {
|
|
1409
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1159
1410
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1160
1411
|
plugin.serverNode = undefined;
|
|
1161
1412
|
}
|
|
@@ -1165,18 +1416,20 @@ export class Frontend extends EventEmitter {
|
|
|
1165
1416
|
this.matterbridge.devices.remove(device);
|
|
1166
1417
|
}
|
|
1167
1418
|
}
|
|
1419
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1168
1420
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1169
1421
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1170
1422
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1171
|
-
plugin.restartRequired = false;
|
|
1423
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1172
1424
|
let needRestart = 0;
|
|
1173
1425
|
for (const plugin of this.matterbridge.plugins) {
|
|
1174
1426
|
if (plugin.restartRequired)
|
|
1175
1427
|
needRestart++;
|
|
1176
1428
|
}
|
|
1177
1429
|
if (needRestart === 0) {
|
|
1178
|
-
this.wssSendRestartNotRequired(true);
|
|
1430
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1179
1431
|
}
|
|
1432
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1180
1433
|
if (plugin.serverNode)
|
|
1181
1434
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1182
1435
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1432,22 +1685,22 @@ export class Frontend extends EventEmitter {
|
|
|
1432
1685
|
if (isValidString(data.params.value, 4)) {
|
|
1433
1686
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1434
1687
|
if (data.params.value === 'Debug') {
|
|
1435
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1688
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1436
1689
|
}
|
|
1437
1690
|
else if (data.params.value === 'Info') {
|
|
1438
|
-
await this.matterbridge.setLogLevel("info");
|
|
1691
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1439
1692
|
}
|
|
1440
1693
|
else if (data.params.value === 'Notice') {
|
|
1441
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1694
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1442
1695
|
}
|
|
1443
1696
|
else if (data.params.value === 'Warn') {
|
|
1444
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1697
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1445
1698
|
}
|
|
1446
1699
|
else if (data.params.value === 'Error') {
|
|
1447
|
-
await this.matterbridge.setLogLevel("error");
|
|
1700
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1448
1701
|
}
|
|
1449
1702
|
else if (data.params.value === 'Fatal') {
|
|
1450
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1703
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1451
1704
|
}
|
|
1452
1705
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1453
1706
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1458,6 +1711,7 @@ export class Frontend extends EventEmitter {
|
|
|
1458
1711
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1459
1712
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1460
1713
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1714
|
+
// Create the file logger for matterbridge
|
|
1461
1715
|
if (data.params.value)
|
|
1462
1716
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1463
1717
|
else
|
|
@@ -1536,6 +1790,7 @@ export class Frontend extends EventEmitter {
|
|
|
1536
1790
|
}
|
|
1537
1791
|
break;
|
|
1538
1792
|
case 'setmatterport':
|
|
1793
|
+
// eslint-disable-next-line no-case-declarations
|
|
1539
1794
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1540
1795
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1541
1796
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1553,6 +1808,7 @@ export class Frontend extends EventEmitter {
|
|
|
1553
1808
|
}
|
|
1554
1809
|
break;
|
|
1555
1810
|
case 'setmatterdiscriminator':
|
|
1811
|
+
// eslint-disable-next-line no-case-declarations
|
|
1556
1812
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1557
1813
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1558
1814
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1570,6 +1826,7 @@ export class Frontend extends EventEmitter {
|
|
|
1570
1826
|
}
|
|
1571
1827
|
break;
|
|
1572
1828
|
case 'setmatterpasscode':
|
|
1829
|
+
// eslint-disable-next-line no-case-declarations
|
|
1573
1830
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1574
1831
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1575
1832
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -1613,15 +1870,19 @@ export class Frontend extends EventEmitter {
|
|
|
1613
1870
|
return;
|
|
1614
1871
|
}
|
|
1615
1872
|
const config = plugin.configJson;
|
|
1873
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1616
1874
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1875
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1617
1876
|
if (select === 'serial')
|
|
1618
1877
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1619
1878
|
if (select === 'name')
|
|
1620
1879
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1621
1880
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1881
|
+
// Remove postfix from the serial if it exists
|
|
1622
1882
|
if (config.postfix) {
|
|
1623
1883
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1624
1884
|
}
|
|
1885
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1625
1886
|
if (isValidArray(config.whiteList, 1)) {
|
|
1626
1887
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1627
1888
|
config.whiteList.push(data.params.serial);
|
|
@@ -1630,6 +1891,7 @@ export class Frontend extends EventEmitter {
|
|
|
1630
1891
|
config.whiteList.push(data.params.name);
|
|
1631
1892
|
}
|
|
1632
1893
|
}
|
|
1894
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1633
1895
|
if (isValidArray(config.blackList, 1)) {
|
|
1634
1896
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1635
1897
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1657,7 +1919,9 @@ export class Frontend extends EventEmitter {
|
|
|
1657
1919
|
return;
|
|
1658
1920
|
}
|
|
1659
1921
|
const config = plugin.configJson;
|
|
1922
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1660
1923
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1924
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1661
1925
|
if (select === 'serial')
|
|
1662
1926
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1663
1927
|
if (select === 'name')
|
|
@@ -1666,6 +1930,7 @@ export class Frontend extends EventEmitter {
|
|
|
1666
1930
|
if (config.postfix) {
|
|
1667
1931
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1668
1932
|
}
|
|
1933
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1669
1934
|
if (isValidArray(config.whiteList, 1)) {
|
|
1670
1935
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1671
1936
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1674,6 +1939,7 @@ export class Frontend extends EventEmitter {
|
|
|
1674
1939
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1675
1940
|
}
|
|
1676
1941
|
}
|
|
1942
|
+
// Add the serial to the blackList
|
|
1677
1943
|
if (isValidArray(config.blackList)) {
|
|
1678
1944
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1679
1945
|
config.blackList.push(data.params.serial);
|
|
@@ -1696,6 +1962,7 @@ export class Frontend extends EventEmitter {
|
|
|
1696
1962
|
}
|
|
1697
1963
|
}
|
|
1698
1964
|
else {
|
|
1965
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1699
1966
|
const localData = data;
|
|
1700
1967
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1701
1968
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1705,80 +1972,206 @@ export class Frontend extends EventEmitter {
|
|
|
1705
1972
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1706
1973
|
}
|
|
1707
1974
|
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1977
|
+
*
|
|
1978
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1979
|
+
* @param {string} time - The time string of the message
|
|
1980
|
+
* @param {string} name - The logger name of the message
|
|
1981
|
+
* @param {string} message - The content of the message.
|
|
1982
|
+
*
|
|
1983
|
+
* @remarks
|
|
1984
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1985
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1986
|
+
* The function sends the message to all connected clients.
|
|
1987
|
+
*/
|
|
1708
1988
|
wssSendLogMessage(level, time, name, message) {
|
|
1709
1989
|
if (!level || !time || !name || !message)
|
|
1710
1990
|
return;
|
|
1991
|
+
// Remove ANSI escape codes from the message
|
|
1992
|
+
// eslint-disable-next-line no-control-regex
|
|
1711
1993
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1994
|
+
// Remove leading asterisks from the message
|
|
1712
1995
|
message = message.replace(/^\*+/, '');
|
|
1996
|
+
// Replace all occurrences of \t and \n
|
|
1713
1997
|
message = message.replace(/[\t\n]/g, '');
|
|
1998
|
+
// Remove non-printable characters
|
|
1999
|
+
// eslint-disable-next-line no-control-regex
|
|
1714
2000
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2001
|
+
// Replace all occurrences of \" with "
|
|
1715
2002
|
message = message.replace(/\\"/g, '"');
|
|
2003
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1716
2004
|
const maxContinuousLength = 100;
|
|
1717
2005
|
const keepStartLength = 20;
|
|
1718
2006
|
const keepEndLength = 20;
|
|
1719
|
-
message
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2007
|
+
// Split the message into words
|
|
2008
|
+
if (level !== 'spawn') {
|
|
2009
|
+
message = message
|
|
2010
|
+
.split(' ')
|
|
2011
|
+
.map((word) => {
|
|
2012
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2013
|
+
if (word.length > maxContinuousLength) {
|
|
2014
|
+
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2015
|
+
}
|
|
2016
|
+
return word;
|
|
2017
|
+
})
|
|
2018
|
+
.join(' ');
|
|
2019
|
+
}
|
|
2020
|
+
// Send the message to all connected clients
|
|
1728
2021
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1729
2022
|
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2025
|
+
*
|
|
2026
|
+
* @param {string} changed - The changed value.
|
|
2027
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2028
|
+
* possible values for changed:
|
|
2029
|
+
* - 'settings'
|
|
2030
|
+
* - 'plugins'
|
|
2031
|
+
* - 'devices'
|
|
2032
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2033
|
+
* @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2034
|
+
*/
|
|
1730
2035
|
wssSendRefreshRequired(changed, params) {
|
|
1731
2036
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2037
|
+
// Send the message to all connected clients
|
|
1732
2038
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1733
2039
|
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2042
|
+
*
|
|
2043
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2044
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2045
|
+
*/
|
|
1734
2046
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1735
2047
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1736
2048
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1737
2049
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1738
2050
|
if (snackbar === true)
|
|
1739
2051
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2052
|
+
// Send the message to all connected clients
|
|
1740
2053
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1741
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2057
|
+
*
|
|
2058
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2059
|
+
*/
|
|
1742
2060
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1743
2061
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1744
2062
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1745
2063
|
if (snackbar === true)
|
|
1746
2064
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2065
|
+
// Send the message to all connected clients
|
|
1747
2066
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1748
2067
|
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2070
|
+
*
|
|
2071
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2072
|
+
*/
|
|
1749
2073
|
wssSendUpdateRequired(devVersion = false) {
|
|
1750
2074
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1751
2075
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2076
|
+
// Send the message to all connected clients
|
|
1752
2077
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1753
2078
|
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Sends a cpu update message to all connected clients.
|
|
2081
|
+
*
|
|
2082
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2083
|
+
*/
|
|
1754
2084
|
wssSendCpuUpdate(cpuUsage) {
|
|
1755
2085
|
if (hasParameter('debug'))
|
|
1756
2086
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2087
|
+
// Send the message to all connected clients
|
|
1757
2088
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
|
|
1758
2089
|
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Sends a memory update message to all connected clients.
|
|
2092
|
+
*
|
|
2093
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2094
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2095
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2096
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2097
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2098
|
+
* @param {string} external - The external memory in bytes.
|
|
2099
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2100
|
+
*/
|
|
1759
2101
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1760
2102
|
if (hasParameter('debug'))
|
|
1761
2103
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2104
|
+
// Send the message to all connected clients
|
|
1762
2105
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1763
2106
|
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Sends an uptime update message to all connected clients.
|
|
2109
|
+
*
|
|
2110
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2111
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2112
|
+
*/
|
|
1764
2113
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1765
2114
|
if (hasParameter('debug'))
|
|
1766
2115
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2116
|
+
// Send the message to all connected clients
|
|
1767
2117
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1768
2118
|
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Sends an open snackbar message to all connected clients.
|
|
2121
|
+
*
|
|
2122
|
+
* @param {string} message - The message to send.
|
|
2123
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2124
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2125
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2126
|
+
*
|
|
2127
|
+
* @remarks
|
|
2128
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2129
|
+
*/
|
|
1769
2130
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1770
2131
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2132
|
+
// Send the message to all connected clients
|
|
1771
2133
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1772
2134
|
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Sends a close snackbar message to all connected clients.
|
|
2137
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2138
|
+
*
|
|
2139
|
+
* @param {string} message - The message to send.
|
|
2140
|
+
*/
|
|
1773
2141
|
wssSendCloseSnackbarMessage(message) {
|
|
1774
2142
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2143
|
+
// Send the message to all connected clients
|
|
1775
2144
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1776
2145
|
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2148
|
+
*
|
|
2149
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2150
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2151
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2152
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2153
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2154
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2155
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2156
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2157
|
+
*
|
|
2158
|
+
* @remarks
|
|
2159
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2160
|
+
* with the updated attribute information.
|
|
2161
|
+
*/
|
|
1777
2162
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1778
2163
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2164
|
+
// Send the message to all connected clients
|
|
1779
2165
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1780
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Sends a message to all connected clients.
|
|
2169
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2170
|
+
*
|
|
2171
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2172
|
+
*/
|
|
1781
2173
|
wssBroadcastMessage(msg) {
|
|
2174
|
+
// Send the message to all connected clients
|
|
1782
2175
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1783
2176
|
if (msg.method !== 'log')
|
|
1784
2177
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -1789,3 +2182,4 @@ export class Frontend extends EventEmitter {
|
|
|
1789
2182
|
});
|
|
1790
2183
|
}
|
|
1791
2184
|
}
|
|
2185
|
+
//# sourceMappingURL=frontend.js.map
|