matterbridge 3.3.0-dev-20251003-626ea2f → 3.3.0
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 +232 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +454 -61
- 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 +430 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +788 -68
- 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 +402 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +340 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +201 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +30 -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 +69 -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/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,16 +29,20 @@ 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
|
-
import { plg } from './matterbridgeTypes.js';
|
|
45
|
+
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from './matterbridgeTypes.js';
|
|
18
46
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
19
47
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
20
48
|
export class Frontend extends EventEmitter {
|
|
@@ -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),
|
|
@@ -304,56 +381,64 @@ export class Frontend extends EventEmitter {
|
|
|
304
381
|
space_available_size: this.formatMemoryUsage(space.space_available_size),
|
|
305
382
|
physical_space_size: this.formatMemoryUsage(space.physical_space_size),
|
|
306
383
|
}));
|
|
307
|
-
const {
|
|
308
|
-
const
|
|
384
|
+
const { createRequire } = await import('node:module');
|
|
385
|
+
const require = createRequire(import.meta.url);
|
|
386
|
+
const cjsModules = Object.keys(require.cache).sort();
|
|
309
387
|
const memoryReport = {
|
|
310
388
|
memoryUsage,
|
|
311
389
|
heapStats,
|
|
312
390
|
heapSpaces,
|
|
313
|
-
|
|
391
|
+
cjsModules,
|
|
314
392
|
};
|
|
315
393
|
res.status(200).json(memoryReport);
|
|
316
394
|
});
|
|
395
|
+
// Endpoint to provide settings
|
|
317
396
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
318
397
|
this.log.debug('The frontend sent /api/settings');
|
|
319
398
|
res.json(await this.getApiSettings());
|
|
320
399
|
});
|
|
400
|
+
// Endpoint to provide plugins
|
|
321
401
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
322
402
|
this.log.debug('The frontend sent /api/plugins');
|
|
323
403
|
res.json(this.getPlugins());
|
|
324
404
|
});
|
|
405
|
+
// Endpoint to provide devices
|
|
325
406
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
326
407
|
this.log.debug('The frontend sent /api/devices');
|
|
327
|
-
const devices =
|
|
408
|
+
const devices = this.getDevices();
|
|
328
409
|
res.json(devices);
|
|
329
410
|
});
|
|
411
|
+
// Endpoint to view the matterbridge log
|
|
330
412
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
331
413
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
332
414
|
try {
|
|
333
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory,
|
|
415
|
+
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
|
|
334
416
|
res.type('text/plain');
|
|
335
417
|
res.send(data);
|
|
336
418
|
}
|
|
337
419
|
catch (error) {
|
|
338
|
-
this.log.error(`Error reading matterbridge log file ${
|
|
420
|
+
this.log.error(`Error reading matterbridge log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
339
421
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
340
422
|
}
|
|
341
423
|
});
|
|
424
|
+
// Endpoint to view the matter.js log
|
|
342
425
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
343
426
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
344
427
|
try {
|
|
345
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory,
|
|
428
|
+
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
|
|
346
429
|
res.type('text/plain');
|
|
347
430
|
res.send(data);
|
|
348
431
|
}
|
|
349
432
|
catch (error) {
|
|
350
|
-
this.log.error(`Error reading matter log file ${
|
|
433
|
+
this.log.error(`Error reading matter log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
351
434
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
352
435
|
}
|
|
353
436
|
});
|
|
437
|
+
// Endpoint to view the diagnostic.log
|
|
354
438
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
355
439
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
356
440
|
const serverNodes = [];
|
|
441
|
+
// istanbul ignore else
|
|
357
442
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
358
443
|
if (this.matterbridge.serverNode)
|
|
359
444
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -364,6 +449,7 @@ export class Frontend extends EventEmitter {
|
|
|
364
449
|
serverNodes.push(plugin.serverNode);
|
|
365
450
|
}
|
|
366
451
|
}
|
|
452
|
+
// istanbul ignore next
|
|
367
453
|
for (const device of this.matterbridge.devices.array()) {
|
|
368
454
|
if (device.serverNode)
|
|
369
455
|
serverNodes.push(device.serverNode);
|
|
@@ -386,17 +472,20 @@ export class Frontend extends EventEmitter {
|
|
|
386
472
|
values: [...serverNodes],
|
|
387
473
|
})));
|
|
388
474
|
delete Logger.destinations.diagnostic;
|
|
389
|
-
await wait(500);
|
|
475
|
+
await wait(500); // Wait for the log to be written
|
|
390
476
|
try {
|
|
391
477
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
|
|
392
478
|
res.type('text/plain');
|
|
393
479
|
res.send(data.slice(29));
|
|
394
480
|
}
|
|
395
481
|
catch (error) {
|
|
396
|
-
|
|
482
|
+
// istanbul ignore next
|
|
483
|
+
this.log.error(`Error reading diagnostic log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
484
|
+
// istanbul ignore next
|
|
397
485
|
res.status(500).send('Error reading diagnostic log file.');
|
|
398
486
|
}
|
|
399
487
|
});
|
|
488
|
+
// Endpoint to view the shelly log
|
|
400
489
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
401
490
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
402
491
|
try {
|
|
@@ -405,48 +494,53 @@ export class Frontend extends EventEmitter {
|
|
|
405
494
|
res.send(data);
|
|
406
495
|
}
|
|
407
496
|
catch (error) {
|
|
408
|
-
this.log.error(`Error reading shelly log file ${
|
|
497
|
+
this.log.error(`Error reading shelly log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
409
498
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
410
499
|
}
|
|
411
500
|
});
|
|
501
|
+
// Endpoint to download the matterbridge log
|
|
412
502
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
413
|
-
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory,
|
|
503
|
+
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
414
504
|
try {
|
|
415
|
-
await fs.access(path.join(this.matterbridge.matterbridgeDirectory,
|
|
416
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory,
|
|
417
|
-
await fs.writeFile(path.join(os.tmpdir(),
|
|
505
|
+
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
|
|
506
|
+
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
|
|
507
|
+
await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
|
|
418
508
|
}
|
|
419
509
|
catch (error) {
|
|
420
|
-
await fs.writeFile(path.join(os.tmpdir(),
|
|
510
|
+
await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
|
|
421
511
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
422
512
|
}
|
|
423
513
|
res.type('text/plain');
|
|
424
|
-
res.download(path.join(os.tmpdir(),
|
|
514
|
+
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
515
|
+
/* istanbul ignore if */
|
|
425
516
|
if (error) {
|
|
426
|
-
this.log.error(`Error downloading log file ${
|
|
517
|
+
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
427
518
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
428
519
|
}
|
|
429
520
|
});
|
|
430
521
|
});
|
|
522
|
+
// Endpoint to download the matter log
|
|
431
523
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
432
|
-
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory,
|
|
524
|
+
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
433
525
|
try {
|
|
434
|
-
await fs.access(path.join(this.matterbridge.matterbridgeDirectory,
|
|
435
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory,
|
|
436
|
-
await fs.writeFile(path.join(os.tmpdir(),
|
|
526
|
+
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
|
|
527
|
+
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
|
|
528
|
+
await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
|
|
437
529
|
}
|
|
438
530
|
catch (error) {
|
|
439
|
-
await fs.writeFile(path.join(os.tmpdir(),
|
|
531
|
+
await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
|
|
440
532
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
441
533
|
}
|
|
442
534
|
res.type('text/plain');
|
|
443
|
-
res.download(path.join(os.tmpdir(),
|
|
535
|
+
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
536
|
+
/* istanbul ignore if */
|
|
444
537
|
if (error) {
|
|
445
|
-
this.log.error(`Error downloading log file ${
|
|
538
|
+
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
446
539
|
res.status(500).send('Error downloading the matter log file');
|
|
447
540
|
}
|
|
448
541
|
});
|
|
449
542
|
});
|
|
543
|
+
// Endpoint to download the shelly log
|
|
450
544
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
451
545
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
452
546
|
try {
|
|
@@ -460,74 +554,90 @@ export class Frontend extends EventEmitter {
|
|
|
460
554
|
}
|
|
461
555
|
res.type('text/plain');
|
|
462
556
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
557
|
+
/* istanbul ignore if */
|
|
463
558
|
if (error) {
|
|
464
559
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
465
560
|
res.status(500).send('Error downloading Shelly system log file');
|
|
466
561
|
}
|
|
467
562
|
});
|
|
468
563
|
});
|
|
564
|
+
// Endpoint to download the matterbridge storage directory
|
|
469
565
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
470
566
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
471
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.${
|
|
472
|
-
res.download(path.join(os.tmpdir(), `matterbridge.${
|
|
567
|
+
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
568
|
+
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
569
|
+
/* istanbul ignore if */
|
|
473
570
|
if (error) {
|
|
474
|
-
this.log.error(`Error downloading file ${`matterbridge.${
|
|
571
|
+
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
475
572
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
476
573
|
}
|
|
477
574
|
});
|
|
478
575
|
});
|
|
576
|
+
// Endpoint to download the matter storage file
|
|
479
577
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
480
578
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
481
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.${
|
|
482
|
-
res.download(path.join(os.tmpdir(), `matterbridge.${
|
|
579
|
+
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
580
|
+
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
581
|
+
/* istanbul ignore if */
|
|
483
582
|
if (error) {
|
|
484
|
-
this.log.error(`Error downloading the matter storage matterbridge.${
|
|
583
|
+
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
485
584
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
486
585
|
}
|
|
487
586
|
});
|
|
488
587
|
});
|
|
588
|
+
// Endpoint to download the matterbridge plugin directory
|
|
489
589
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
490
590
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
491
591
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
492
592
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
593
|
+
/* istanbul ignore if */
|
|
493
594
|
if (error) {
|
|
494
595
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
495
596
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
496
597
|
}
|
|
497
598
|
});
|
|
498
599
|
});
|
|
600
|
+
// Endpoint to download the matterbridge plugin config files
|
|
499
601
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
500
602
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
501
603
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
502
604
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
605
|
+
/* istanbul ignore if */
|
|
503
606
|
if (error) {
|
|
504
607
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
505
608
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
506
609
|
}
|
|
507
610
|
});
|
|
508
611
|
});
|
|
612
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
509
613
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
510
614
|
this.log.debug('The frontend sent /api/download-backup');
|
|
511
615
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
616
|
+
/* istanbul ignore if */
|
|
512
617
|
if (error) {
|
|
513
618
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
514
619
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
515
620
|
}
|
|
516
621
|
});
|
|
517
622
|
});
|
|
623
|
+
// Endpoint to upload a package
|
|
518
624
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
519
625
|
const { filename } = req.body;
|
|
520
626
|
const file = req.file;
|
|
627
|
+
/* istanbul ignore if */
|
|
521
628
|
if (!file || !filename) {
|
|
522
629
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
523
630
|
res.status(400).send('Invalid request: file and filename are required');
|
|
524
631
|
return;
|
|
525
632
|
}
|
|
526
633
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
634
|
+
// Define the path where the plugin file will be saved
|
|
527
635
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
528
636
|
try {
|
|
637
|
+
// Move the uploaded file to the specified path
|
|
529
638
|
await fs.rename(file.path, filePath);
|
|
530
639
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
640
|
+
// Install the plugin package
|
|
531
641
|
if (filename.endsWith('.tgz')) {
|
|
532
642
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
533
643
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
|
|
@@ -547,6 +657,7 @@ export class Frontend extends EventEmitter {
|
|
|
547
657
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
548
658
|
}
|
|
549
659
|
});
|
|
660
|
+
// Fallback for routing (must be the last route)
|
|
550
661
|
this.expressApp.use((req, res) => {
|
|
551
662
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
552
663
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -555,13 +666,16 @@ export class Frontend extends EventEmitter {
|
|
|
555
666
|
}
|
|
556
667
|
async stop() {
|
|
557
668
|
this.log.debug('Stopping the frontend...');
|
|
669
|
+
// Remove listeners from the express app
|
|
558
670
|
if (this.expressApp) {
|
|
559
671
|
this.expressApp.removeAllListeners();
|
|
560
672
|
this.expressApp = undefined;
|
|
561
673
|
this.log.debug('Frontend app closed successfully');
|
|
562
674
|
}
|
|
675
|
+
// Close the WebSocket server
|
|
563
676
|
if (this.webSocketServer) {
|
|
564
677
|
this.log.debug('Closing WebSocket server...');
|
|
678
|
+
// Close all active connections
|
|
565
679
|
this.webSocketServer.clients.forEach((client) => {
|
|
566
680
|
if (client.readyState === WebSocket.OPEN) {
|
|
567
681
|
client.close();
|
|
@@ -570,6 +684,7 @@ export class Frontend extends EventEmitter {
|
|
|
570
684
|
await withTimeout(new Promise((resolve) => {
|
|
571
685
|
this.webSocketServer?.close((error) => {
|
|
572
686
|
if (error) {
|
|
687
|
+
// istanbul ignore next
|
|
573
688
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
574
689
|
}
|
|
575
690
|
else {
|
|
@@ -582,8 +697,27 @@ export class Frontend extends EventEmitter {
|
|
|
582
697
|
this.webSocketServer.removeAllListeners();
|
|
583
698
|
this.webSocketServer = undefined;
|
|
584
699
|
}
|
|
700
|
+
// Close the http server
|
|
585
701
|
if (this.httpServer) {
|
|
586
702
|
this.log.debug('Closing http server...');
|
|
703
|
+
/*
|
|
704
|
+
await withTimeout(
|
|
705
|
+
new Promise<void>((resolve) => {
|
|
706
|
+
this.httpServer?.close((error) => {
|
|
707
|
+
if (error) {
|
|
708
|
+
// istanbul ignore next
|
|
709
|
+
this.log.error(`Error closing http server: ${error}`);
|
|
710
|
+
} else {
|
|
711
|
+
this.log.debug('Http server closed successfully');
|
|
712
|
+
this.emit('server_stopped');
|
|
713
|
+
}
|
|
714
|
+
resolve();
|
|
715
|
+
});
|
|
716
|
+
}),
|
|
717
|
+
5000,
|
|
718
|
+
false,
|
|
719
|
+
);
|
|
720
|
+
*/
|
|
587
721
|
this.httpServer.close();
|
|
588
722
|
this.log.debug('Http server closed successfully');
|
|
589
723
|
this.listening = false;
|
|
@@ -592,8 +726,27 @@ export class Frontend extends EventEmitter {
|
|
|
592
726
|
this.httpServer = undefined;
|
|
593
727
|
this.log.debug('Frontend http server closed successfully');
|
|
594
728
|
}
|
|
729
|
+
// Close the https server
|
|
595
730
|
if (this.httpsServer) {
|
|
596
731
|
this.log.debug('Closing https server...');
|
|
732
|
+
/*
|
|
733
|
+
await withTimeout(
|
|
734
|
+
new Promise<void>((resolve) => {
|
|
735
|
+
this.httpsServer?.close((error) => {
|
|
736
|
+
if (error) {
|
|
737
|
+
// istanbul ignore next
|
|
738
|
+
this.log.error(`Error closing https server: ${error}`);
|
|
739
|
+
} else {
|
|
740
|
+
this.log.debug('Https server closed successfully');
|
|
741
|
+
this.emit('server_stopped');
|
|
742
|
+
}
|
|
743
|
+
resolve();
|
|
744
|
+
});
|
|
745
|
+
}),
|
|
746
|
+
5000,
|
|
747
|
+
false,
|
|
748
|
+
);
|
|
749
|
+
*/
|
|
597
750
|
this.httpsServer.close();
|
|
598
751
|
this.log.debug('Https server closed successfully');
|
|
599
752
|
this.listening = false;
|
|
@@ -604,6 +757,7 @@ export class Frontend extends EventEmitter {
|
|
|
604
757
|
}
|
|
605
758
|
this.log.debug('Frontend stopped successfully');
|
|
606
759
|
}
|
|
760
|
+
// Function to format bytes to KB, MB, or GB
|
|
607
761
|
formatMemoryUsage = (bytes) => {
|
|
608
762
|
if (bytes >= 1024 ** 3) {
|
|
609
763
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -615,6 +769,7 @@ export class Frontend extends EventEmitter {
|
|
|
615
769
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
616
770
|
}
|
|
617
771
|
};
|
|
772
|
+
// Function to format system uptime with only the most significant unit
|
|
618
773
|
formatOsUpTime = (seconds) => {
|
|
619
774
|
if (seconds >= 86400) {
|
|
620
775
|
const days = Math.floor(seconds / 86400);
|
|
@@ -630,7 +785,13 @@ export class Frontend extends EventEmitter {
|
|
|
630
785
|
}
|
|
631
786
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
632
787
|
};
|
|
788
|
+
/**
|
|
789
|
+
* Retrieves the api settings data.
|
|
790
|
+
*
|
|
791
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
792
|
+
*/
|
|
633
793
|
async getApiSettings() {
|
|
794
|
+
// Update the system information
|
|
634
795
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
635
796
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
636
797
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -639,6 +800,7 @@ export class Frontend extends EventEmitter {
|
|
|
639
800
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
640
801
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
641
802
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
803
|
+
// Update the matterbridge information
|
|
642
804
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
643
805
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
644
806
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -652,6 +814,12 @@ export class Frontend extends EventEmitter {
|
|
|
652
814
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
653
815
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
654
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* Retrieves the reachable attribute.
|
|
819
|
+
*
|
|
820
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
821
|
+
* @returns {boolean} The reachable attribute.
|
|
822
|
+
*/
|
|
655
823
|
getReachability(device) {
|
|
656
824
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
657
825
|
return false;
|
|
@@ -663,6 +831,12 @@ export class Frontend extends EventEmitter {
|
|
|
663
831
|
return true;
|
|
664
832
|
return false;
|
|
665
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* Retrieves the power source attribute.
|
|
836
|
+
*
|
|
837
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
838
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
839
|
+
*/
|
|
666
840
|
getPowerSource(endpoint) {
|
|
667
841
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
668
842
|
return undefined;
|
|
@@ -678,13 +852,22 @@ export class Frontend extends EventEmitter {
|
|
|
678
852
|
}
|
|
679
853
|
return;
|
|
680
854
|
};
|
|
855
|
+
// Root endpoint
|
|
681
856
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
682
857
|
return powerSource(endpoint);
|
|
858
|
+
// Child endpoints
|
|
683
859
|
for (const child of endpoint.getChildEndpoints()) {
|
|
684
860
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
685
861
|
return powerSource(child);
|
|
686
862
|
}
|
|
687
863
|
}
|
|
864
|
+
/**
|
|
865
|
+
* Retrieves the cluster text description from a given device.
|
|
866
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
867
|
+
*
|
|
868
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
869
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
870
|
+
*/
|
|
688
871
|
getClusterTextFromDevice(device) {
|
|
689
872
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
690
873
|
return '';
|
|
@@ -695,6 +878,7 @@ export class Frontend extends EventEmitter {
|
|
|
695
878
|
if (composed)
|
|
696
879
|
return 'Composed: ' + composed.value;
|
|
697
880
|
}
|
|
881
|
+
// istanbul ignore next cause is not reachable
|
|
698
882
|
return '';
|
|
699
883
|
};
|
|
700
884
|
const getFixedLabel = (device) => {
|
|
@@ -704,11 +888,13 @@ export class Frontend extends EventEmitter {
|
|
|
704
888
|
if (composed)
|
|
705
889
|
return 'Composed: ' + composed.value;
|
|
706
890
|
}
|
|
891
|
+
// istanbul ignore next cause is not reacheable
|
|
707
892
|
return '';
|
|
708
893
|
};
|
|
709
894
|
let attributes = '';
|
|
710
895
|
let supportedModes = [];
|
|
711
896
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
897
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
712
898
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
713
899
|
return;
|
|
714
900
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -798,11 +984,17 @@ export class Frontend extends EventEmitter {
|
|
|
798
984
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
799
985
|
attributes += `${getUserLabel(device)} `;
|
|
800
986
|
});
|
|
987
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
801
988
|
return attributes.trimStart().trimEnd();
|
|
802
989
|
}
|
|
990
|
+
/**
|
|
991
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
992
|
+
*
|
|
993
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
994
|
+
*/
|
|
803
995
|
getPlugins() {
|
|
804
996
|
if (this.matterbridge.hasCleanupStarted)
|
|
805
|
-
return [];
|
|
997
|
+
return []; // Skip if cleanup has started
|
|
806
998
|
const baseRegisteredPlugins = [];
|
|
807
999
|
for (const plugin of this.matterbridge.plugins) {
|
|
808
1000
|
baseRegisteredPlugins.push({
|
|
@@ -830,18 +1022,27 @@ export class Frontend extends EventEmitter {
|
|
|
830
1022
|
schemaJson: plugin.schemaJson,
|
|
831
1023
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
832
1024
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1025
|
+
// Childbridge mode specific data
|
|
833
1026
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
834
1027
|
});
|
|
835
1028
|
}
|
|
836
1029
|
return baseRegisteredPlugins;
|
|
837
1030
|
}
|
|
838
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* Retrieves the devices from Matterbridge.
|
|
1033
|
+
*
|
|
1034
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1035
|
+
* @returns {ApiDevices[]} An array of ApiDevices for the frontend.
|
|
1036
|
+
*/
|
|
1037
|
+
getDevices(pluginName) {
|
|
839
1038
|
if (this.matterbridge.hasCleanupStarted)
|
|
840
|
-
return [];
|
|
1039
|
+
return []; // Skip if cleanup has started
|
|
841
1040
|
const devices = [];
|
|
842
1041
|
for (const device of this.matterbridge.devices.array()) {
|
|
1042
|
+
// Filter by pluginName if provided
|
|
843
1043
|
if (pluginName && pluginName !== device.plugin)
|
|
844
1044
|
continue;
|
|
1045
|
+
// Check if the device has the required properties
|
|
845
1046
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
846
1047
|
continue;
|
|
847
1048
|
devices.push({
|
|
@@ -861,24 +1062,39 @@ export class Frontend extends EventEmitter {
|
|
|
861
1062
|
}
|
|
862
1063
|
return devices;
|
|
863
1064
|
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1067
|
+
*
|
|
1068
|
+
* Response for /api/clusters
|
|
1069
|
+
*
|
|
1070
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1071
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1072
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1073
|
+
*/
|
|
864
1074
|
getClusters(pluginName, endpointNumber) {
|
|
865
1075
|
if (this.matterbridge.hasCleanupStarted)
|
|
866
|
-
return;
|
|
1076
|
+
return; // Skip if cleanup has started
|
|
867
1077
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
868
1078
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
869
1079
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
870
1080
|
return;
|
|
871
1081
|
}
|
|
1082
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1083
|
+
// Get the device types from the main endpoint
|
|
872
1084
|
const deviceTypes = [];
|
|
873
1085
|
const clusters = [];
|
|
874
1086
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
875
1087
|
deviceTypes.push(d.deviceType);
|
|
876
1088
|
});
|
|
1089
|
+
// Get the clusters from the main endpoint
|
|
877
1090
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
878
1091
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
879
1092
|
return;
|
|
880
1093
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
881
1094
|
return;
|
|
1095
|
+
// console.log(
|
|
1096
|
+
// `${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}`,
|
|
1097
|
+
// );
|
|
882
1098
|
clusters.push({
|
|
883
1099
|
endpoint: endpoint.number.toString(),
|
|
884
1100
|
number: endpoint.number,
|
|
@@ -892,12 +1108,19 @@ export class Frontend extends EventEmitter {
|
|
|
892
1108
|
attributeLocalValue: attributeValue,
|
|
893
1109
|
});
|
|
894
1110
|
});
|
|
1111
|
+
// Get the child endpoints
|
|
895
1112
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1113
|
+
// if (childEndpoints.length === 0) {
|
|
1114
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1115
|
+
// }
|
|
896
1116
|
childEndpoints.forEach((childEndpoint) => {
|
|
1117
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
897
1118
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
898
1119
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
899
1120
|
return;
|
|
900
1121
|
}
|
|
1122
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1123
|
+
// Get the device types of the child endpoint
|
|
901
1124
|
const deviceTypes = [];
|
|
902
1125
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
903
1126
|
deviceTypes.push(d.deviceType);
|
|
@@ -907,6 +1130,9 @@ export class Frontend extends EventEmitter {
|
|
|
907
1130
|
return;
|
|
908
1131
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
909
1132
|
return;
|
|
1133
|
+
// console.log(
|
|
1134
|
+
// `${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}`,
|
|
1135
|
+
// );
|
|
910
1136
|
clusters.push({
|
|
911
1137
|
endpoint: childEndpoint.number.toString(),
|
|
912
1138
|
number: childEndpoint.number,
|
|
@@ -923,6 +1149,13 @@ export class Frontend extends EventEmitter {
|
|
|
923
1149
|
});
|
|
924
1150
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
|
|
925
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1154
|
+
*
|
|
1155
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1156
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1157
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1158
|
+
*/
|
|
926
1159
|
async wsMessageHandler(client, message) {
|
|
927
1160
|
let data;
|
|
928
1161
|
const sendResponse = (data) => {
|
|
@@ -942,7 +1175,7 @@ export class Frontend extends EventEmitter {
|
|
|
942
1175
|
};
|
|
943
1176
|
try {
|
|
944
1177
|
data = JSON.parse(message.toString());
|
|
945
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1178
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) /* || !isValidObject(data.params)*/ || data.dst !== 'Matterbridge') {
|
|
946
1179
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
947
1180
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
948
1181
|
return;
|
|
@@ -985,36 +1218,49 @@ export class Frontend extends EventEmitter {
|
|
|
985
1218
|
this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
|
|
986
1219
|
const packageName = localData.params.packageName.replace(/@.*$/, '');
|
|
987
1220
|
if (localData.params.restart === false && packageName !== 'matterbridge') {
|
|
1221
|
+
// The install comes from InstallPlugins
|
|
988
1222
|
this.matterbridge.plugins
|
|
989
1223
|
.add(packageName)
|
|
990
1224
|
.then((plugin) => {
|
|
1225
|
+
// istanbul ignore next if
|
|
991
1226
|
if (plugin) {
|
|
1227
|
+
// The plugin is not registered
|
|
992
1228
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
1229
|
+
// In childbridge mode the plugins server node is not started when added
|
|
993
1230
|
if (this.matterbridge.bridgeMode === 'childbridge')
|
|
994
1231
|
this.wssSendRestartRequired(true, true);
|
|
995
1232
|
this.matterbridge.plugins
|
|
996
1233
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1234
|
+
// eslint-disable-next-line promise/no-nesting
|
|
997
1235
|
.then(() => {
|
|
998
1236
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
999
1237
|
this.wssSendRefreshRequired('plugins');
|
|
1000
1238
|
this.wssSendRefreshRequired('devices');
|
|
1001
1239
|
return;
|
|
1002
1240
|
})
|
|
1241
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1003
1242
|
.catch((_error) => {
|
|
1243
|
+
//
|
|
1004
1244
|
});
|
|
1005
1245
|
}
|
|
1006
1246
|
else {
|
|
1247
|
+
// The plugin is already registered
|
|
1007
1248
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1008
1249
|
this.wssSendRefreshRequired('plugins');
|
|
1009
1250
|
this.wssSendRestartRequired(true, true);
|
|
1010
1251
|
}
|
|
1011
1252
|
return;
|
|
1012
1253
|
})
|
|
1254
|
+
// eslint-disable-next-line promise/no-nesting
|
|
1013
1255
|
.catch((_error) => {
|
|
1256
|
+
//
|
|
1014
1257
|
});
|
|
1015
1258
|
}
|
|
1016
1259
|
else {
|
|
1260
|
+
// The package is matterbridge
|
|
1261
|
+
// istanbul ignore next
|
|
1017
1262
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1263
|
+
// istanbul ignore next if
|
|
1018
1264
|
if (this.matterbridge.restartMode !== '') {
|
|
1019
1265
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
1020
1266
|
this.matterbridge.shutdownProcess();
|
|
@@ -1037,7 +1283,9 @@ export class Frontend extends EventEmitter {
|
|
|
1037
1283
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
|
|
1038
1284
|
return;
|
|
1039
1285
|
}
|
|
1286
|
+
// The package is a plugin
|
|
1040
1287
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1288
|
+
// istanbul ignore next if
|
|
1041
1289
|
if (plugin) {
|
|
1042
1290
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1043
1291
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1045,6 +1293,7 @@ export class Frontend extends EventEmitter {
|
|
|
1045
1293
|
this.wssSendRefreshRequired('plugins');
|
|
1046
1294
|
this.wssSendRefreshRequired('devices');
|
|
1047
1295
|
}
|
|
1296
|
+
// Uninstall the package
|
|
1048
1297
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1049
1298
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1050
1299
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
|
|
@@ -1087,6 +1336,7 @@ export class Frontend extends EventEmitter {
|
|
|
1087
1336
|
return;
|
|
1088
1337
|
})
|
|
1089
1338
|
.catch((_error) => {
|
|
1339
|
+
//
|
|
1090
1340
|
});
|
|
1091
1341
|
}
|
|
1092
1342
|
else {
|
|
@@ -1134,6 +1384,7 @@ export class Frontend extends EventEmitter {
|
|
|
1134
1384
|
return;
|
|
1135
1385
|
})
|
|
1136
1386
|
.catch((_error) => {
|
|
1387
|
+
//
|
|
1137
1388
|
});
|
|
1138
1389
|
}
|
|
1139
1390
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1159,6 +1410,7 @@ export class Frontend extends EventEmitter {
|
|
|
1159
1410
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1160
1411
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1161
1412
|
if (plugin.serverNode) {
|
|
1413
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1162
1414
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1163
1415
|
plugin.serverNode = undefined;
|
|
1164
1416
|
}
|
|
@@ -1168,18 +1420,20 @@ export class Frontend extends EventEmitter {
|
|
|
1168
1420
|
this.matterbridge.devices.remove(device);
|
|
1169
1421
|
}
|
|
1170
1422
|
}
|
|
1423
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1171
1424
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1172
1425
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1173
1426
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1174
|
-
plugin.restartRequired = false;
|
|
1427
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1175
1428
|
let needRestart = 0;
|
|
1176
1429
|
for (const plugin of this.matterbridge.plugins) {
|
|
1177
1430
|
if (plugin.restartRequired)
|
|
1178
1431
|
needRestart++;
|
|
1179
1432
|
}
|
|
1180
1433
|
if (needRestart === 0) {
|
|
1181
|
-
this.wssSendRestartNotRequired(true);
|
|
1434
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1182
1435
|
}
|
|
1436
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1183
1437
|
if (plugin.serverNode)
|
|
1184
1438
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1185
1439
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1336,7 +1590,7 @@ export class Frontend extends EventEmitter {
|
|
|
1336
1590
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
|
|
1337
1591
|
}
|
|
1338
1592
|
else if (data.method === '/api/devices') {
|
|
1339
|
-
const devices =
|
|
1593
|
+
const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
1340
1594
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
|
|
1341
1595
|
}
|
|
1342
1596
|
else if (data.method === '/api/clusters') {
|
|
@@ -1436,22 +1690,22 @@ export class Frontend extends EventEmitter {
|
|
|
1436
1690
|
if (isValidString(data.params.value, 4)) {
|
|
1437
1691
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1438
1692
|
if (data.params.value === 'Debug') {
|
|
1439
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1693
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1440
1694
|
}
|
|
1441
1695
|
else if (data.params.value === 'Info') {
|
|
1442
|
-
await this.matterbridge.setLogLevel("info");
|
|
1696
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1443
1697
|
}
|
|
1444
1698
|
else if (data.params.value === 'Notice') {
|
|
1445
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1699
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1446
1700
|
}
|
|
1447
1701
|
else if (data.params.value === 'Warn') {
|
|
1448
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1702
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1449
1703
|
}
|
|
1450
1704
|
else if (data.params.value === 'Error') {
|
|
1451
|
-
await this.matterbridge.setLogLevel("error");
|
|
1705
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1452
1706
|
}
|
|
1453
1707
|
else if (data.params.value === 'Fatal') {
|
|
1454
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1708
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1455
1709
|
}
|
|
1456
1710
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1457
1711
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1462,8 +1716,9 @@ export class Frontend extends EventEmitter {
|
|
|
1462
1716
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1463
1717
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1464
1718
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1719
|
+
// Create the file logger for matterbridge
|
|
1465
1720
|
if (data.params.value)
|
|
1466
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory,
|
|
1721
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1467
1722
|
else
|
|
1468
1723
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
1469
1724
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1501,7 +1756,7 @@ export class Frontend extends EventEmitter {
|
|
|
1501
1756
|
this.matterbridge.matterbridgeInformation.matterFileLogger = data.params.value;
|
|
1502
1757
|
await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
|
|
1503
1758
|
if (data.params.value) {
|
|
1504
|
-
this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory,
|
|
1759
|
+
this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
1505
1760
|
}
|
|
1506
1761
|
else {
|
|
1507
1762
|
this.matterbridge.matterLog.logFilePath = undefined;
|
|
@@ -1540,6 +1795,7 @@ export class Frontend extends EventEmitter {
|
|
|
1540
1795
|
}
|
|
1541
1796
|
break;
|
|
1542
1797
|
case 'setmatterport':
|
|
1798
|
+
// eslint-disable-next-line no-case-declarations
|
|
1543
1799
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1544
1800
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1545
1801
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
@@ -1557,6 +1813,7 @@ export class Frontend extends EventEmitter {
|
|
|
1557
1813
|
}
|
|
1558
1814
|
break;
|
|
1559
1815
|
case 'setmatterdiscriminator':
|
|
1816
|
+
// eslint-disable-next-line no-case-declarations
|
|
1560
1817
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1561
1818
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1562
1819
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
@@ -1574,6 +1831,7 @@ export class Frontend extends EventEmitter {
|
|
|
1574
1831
|
}
|
|
1575
1832
|
break;
|
|
1576
1833
|
case 'setmatterpasscode':
|
|
1834
|
+
// eslint-disable-next-line no-case-declarations
|
|
1577
1835
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1578
1836
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1579
1837
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -1617,15 +1875,19 @@ export class Frontend extends EventEmitter {
|
|
|
1617
1875
|
return;
|
|
1618
1876
|
}
|
|
1619
1877
|
const config = plugin.configJson;
|
|
1878
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1620
1879
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1880
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1621
1881
|
if (select === 'serial')
|
|
1622
1882
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1623
1883
|
if (select === 'name')
|
|
1624
1884
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1625
1885
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1886
|
+
// Remove postfix from the serial if it exists
|
|
1626
1887
|
if (config.postfix) {
|
|
1627
1888
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1628
1889
|
}
|
|
1890
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1629
1891
|
if (isValidArray(config.whiteList, 1)) {
|
|
1630
1892
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1631
1893
|
config.whiteList.push(data.params.serial);
|
|
@@ -1634,6 +1896,7 @@ export class Frontend extends EventEmitter {
|
|
|
1634
1896
|
config.whiteList.push(data.params.name);
|
|
1635
1897
|
}
|
|
1636
1898
|
}
|
|
1899
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1637
1900
|
if (isValidArray(config.blackList, 1)) {
|
|
1638
1901
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1639
1902
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1661,7 +1924,9 @@ export class Frontend extends EventEmitter {
|
|
|
1661
1924
|
return;
|
|
1662
1925
|
}
|
|
1663
1926
|
const config = plugin.configJson;
|
|
1927
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1664
1928
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1929
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1665
1930
|
if (select === 'serial')
|
|
1666
1931
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1667
1932
|
if (select === 'name')
|
|
@@ -1670,6 +1935,7 @@ export class Frontend extends EventEmitter {
|
|
|
1670
1935
|
if (config.postfix) {
|
|
1671
1936
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1672
1937
|
}
|
|
1938
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1673
1939
|
if (isValidArray(config.whiteList, 1)) {
|
|
1674
1940
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1675
1941
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1678,6 +1944,7 @@ export class Frontend extends EventEmitter {
|
|
|
1678
1944
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1679
1945
|
}
|
|
1680
1946
|
}
|
|
1947
|
+
// Add the serial to the blackList
|
|
1681
1948
|
if (isValidArray(config.blackList)) {
|
|
1682
1949
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1683
1950
|
config.blackList.push(data.params.serial);
|
|
@@ -1700,6 +1967,7 @@ export class Frontend extends EventEmitter {
|
|
|
1700
1967
|
}
|
|
1701
1968
|
}
|
|
1702
1969
|
else {
|
|
1970
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1703
1971
|
const localData = data;
|
|
1704
1972
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1705
1973
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1709,21 +1977,44 @@ export class Frontend extends EventEmitter {
|
|
|
1709
1977
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1710
1978
|
}
|
|
1711
1979
|
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1982
|
+
*
|
|
1983
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1984
|
+
* @param {string} time - The time string of the message
|
|
1985
|
+
* @param {string} name - The logger name of the message
|
|
1986
|
+
* @param {string} message - The content of the message.
|
|
1987
|
+
*
|
|
1988
|
+
* @remarks
|
|
1989
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1990
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1991
|
+
* The function sends the message to all connected clients.
|
|
1992
|
+
*/
|
|
1712
1993
|
wssSendLogMessage(level, time, name, message) {
|
|
1713
1994
|
if (!level || !time || !name || !message)
|
|
1714
1995
|
return;
|
|
1996
|
+
// Remove ANSI escape codes from the message
|
|
1997
|
+
// eslint-disable-next-line no-control-regex
|
|
1715
1998
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1999
|
+
// Remove leading asterisks from the message
|
|
1716
2000
|
message = message.replace(/^\*+/, '');
|
|
2001
|
+
// Replace all occurrences of \t and \n
|
|
1717
2002
|
message = message.replace(/[\t\n]/g, '');
|
|
2003
|
+
// Remove non-printable characters
|
|
2004
|
+
// eslint-disable-next-line no-control-regex
|
|
1718
2005
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2006
|
+
// Replace all occurrences of \" with "
|
|
1719
2007
|
message = message.replace(/\\"/g, '"');
|
|
2008
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1720
2009
|
const maxContinuousLength = 100;
|
|
1721
2010
|
const keepStartLength = 20;
|
|
1722
2011
|
const keepEndLength = 20;
|
|
2012
|
+
// Split the message into words
|
|
1723
2013
|
if (level !== 'spawn') {
|
|
1724
2014
|
message = message
|
|
1725
2015
|
.split(' ')
|
|
1726
2016
|
.map((word) => {
|
|
2017
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1727
2018
|
if (word.length > maxContinuousLength) {
|
|
1728
2019
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1729
2020
|
}
|
|
@@ -1731,60 +2022,161 @@ export class Frontend extends EventEmitter {
|
|
|
1731
2022
|
})
|
|
1732
2023
|
.join(' ');
|
|
1733
2024
|
}
|
|
2025
|
+
// Send the message to all connected clients
|
|
1734
2026
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
1735
2027
|
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2030
|
+
*
|
|
2031
|
+
* @param {string} changed - The changed value.
|
|
2032
|
+
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2033
|
+
* possible values for changed:
|
|
2034
|
+
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2035
|
+
* - 'plugins'
|
|
2036
|
+
* - 'devices'
|
|
2037
|
+
* - 'matter' with param 'matter' (QRDiv component)
|
|
2038
|
+
* @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2039
|
+
*/
|
|
1736
2040
|
wssSendRefreshRequired(changed, params) {
|
|
1737
2041
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2042
|
+
// Send the message to all connected clients
|
|
1738
2043
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
1739
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2047
|
+
*
|
|
2048
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2049
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2050
|
+
*/
|
|
1740
2051
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1741
2052
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1742
2053
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1743
2054
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1744
2055
|
if (snackbar === true)
|
|
1745
2056
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2057
|
+
// Send the message to all connected clients
|
|
1746
2058
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
1747
2059
|
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2062
|
+
*
|
|
2063
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2064
|
+
*/
|
|
1748
2065
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1749
2066
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1750
2067
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1751
2068
|
if (snackbar === true)
|
|
1752
2069
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2070
|
+
// Send the message to all connected clients
|
|
1753
2071
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
1754
2072
|
}
|
|
2073
|
+
/**
|
|
2074
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2075
|
+
*
|
|
2076
|
+
* @param {boolean} devVersion - If true, the update is for a development version. Default is false.
|
|
2077
|
+
*/
|
|
1755
2078
|
wssSendUpdateRequired(devVersion = false) {
|
|
1756
2079
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1757
2080
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2081
|
+
// Send the message to all connected clients
|
|
1758
2082
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
1759
2083
|
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Sends a cpu update message to all connected clients.
|
|
2086
|
+
*
|
|
2087
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2088
|
+
*/
|
|
1760
2089
|
wssSendCpuUpdate(cpuUsage) {
|
|
1761
2090
|
if (hasParameter('debug'))
|
|
1762
2091
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2092
|
+
// Send the message to all connected clients
|
|
1763
2093
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
|
|
1764
2094
|
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Sends a memory update message to all connected clients.
|
|
2097
|
+
*
|
|
2098
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2099
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2100
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2101
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2102
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2103
|
+
* @param {string} external - The external memory in bytes.
|
|
2104
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2105
|
+
*/
|
|
1765
2106
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1766
2107
|
if (hasParameter('debug'))
|
|
1767
2108
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2109
|
+
// Send the message to all connected clients
|
|
1768
2110
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
1769
2111
|
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Sends an uptime update message to all connected clients.
|
|
2114
|
+
*
|
|
2115
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2116
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2117
|
+
*/
|
|
1770
2118
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1771
2119
|
if (hasParameter('debug'))
|
|
1772
2120
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2121
|
+
// Send the message to all connected clients
|
|
1773
2122
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
1774
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Sends an open snackbar message to all connected clients.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {string} message - The message to send.
|
|
2128
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2129
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2130
|
+
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2131
|
+
*
|
|
2132
|
+
* @remarks
|
|
2133
|
+
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2134
|
+
*/
|
|
1775
2135
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1776
2136
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2137
|
+
// Send the message to all connected clients
|
|
1777
2138
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
1778
2139
|
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Sends a close snackbar message to all connected clients.
|
|
2142
|
+
* It will close the snackbar message with the same message and timeout = 0.
|
|
2143
|
+
*
|
|
2144
|
+
* @param {string} message - The message to send.
|
|
2145
|
+
*/
|
|
1779
2146
|
wssSendCloseSnackbarMessage(message) {
|
|
1780
2147
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2148
|
+
// Send the message to all connected clients
|
|
1781
2149
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
1782
2150
|
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2153
|
+
*
|
|
2154
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2155
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2156
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2157
|
+
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2158
|
+
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2159
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2160
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2161
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2162
|
+
*
|
|
2163
|
+
* @remarks
|
|
2164
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2165
|
+
* with the updated attribute information.
|
|
2166
|
+
*/
|
|
1783
2167
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
1784
2168
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2169
|
+
// Send the message to all connected clients
|
|
1785
2170
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
1786
2171
|
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Sends a message to all connected clients.
|
|
2174
|
+
* This is an helper function to send a broadcast message to all connected clients.
|
|
2175
|
+
*
|
|
2176
|
+
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2177
|
+
*/
|
|
1787
2178
|
wssBroadcastMessage(msg) {
|
|
2179
|
+
// Send the message to all connected clients
|
|
1788
2180
|
const stringifiedMsg = JSON.stringify(msg);
|
|
1789
2181
|
if (msg.method !== 'log')
|
|
1790
2182
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -1795,3 +2187,4 @@ export class Frontend extends EventEmitter {
|
|
|
1795
2187
|
});
|
|
1796
2188
|
}
|
|
1797
2189
|
}
|
|
2190
|
+
//# sourceMappingURL=frontend.js.map
|