matterbridge 3.1.2-dev-20250707-800ff6d → 3.1.2
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 +0 -1
- 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/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/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 +9 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.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 +87 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +83 -6
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +242 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +91 -7
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +110 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +89 -6
- 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/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/frontend.d.ts +303 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +417 -16
- package/dist/frontend.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 +450 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +802 -50
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1340 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +61 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +709 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +579 -15
- 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 +1196 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1053 -42
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +322 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +310 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +233 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +192 -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 +291 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +269 -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 +59 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +54 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +117 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +263 -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/export.d.ts +12 -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 +49 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +58 -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/network.d.ts +76 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +83 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +11 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +18 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +56 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +62 -9
- 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,30 +1,126 @@
|
|
|
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.1.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';
|
|
4
28
|
import path from 'node:path';
|
|
5
29
|
import { promises as fs } from 'node:fs';
|
|
6
30
|
import EventEmitter from 'node:events';
|
|
31
|
+
// Third-party modules
|
|
7
32
|
import express from 'express';
|
|
8
33
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
34
|
import multer from 'multer';
|
|
35
|
+
// AnsiLogger module
|
|
10
36
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
37
|
+
// @matter
|
|
11
38
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
12
39
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
|
|
14
42
|
import { plg } from './matterbridgeTypes.js';
|
|
15
43
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
16
44
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
45
|
+
/**
|
|
46
|
+
* Websocket message ID for logging.
|
|
47
|
+
*
|
|
48
|
+
* @constant {number}
|
|
49
|
+
*/
|
|
17
50
|
export const WS_ID_LOG = 0;
|
|
51
|
+
/**
|
|
52
|
+
* Websocket message ID indicating a refresh is needed.
|
|
53
|
+
*
|
|
54
|
+
* @constant {number}
|
|
55
|
+
*/
|
|
18
56
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Websocket message ID indicating a restart is needed.
|
|
59
|
+
*
|
|
60
|
+
* @constant {number}
|
|
61
|
+
*/
|
|
19
62
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
63
|
+
/**
|
|
64
|
+
* Websocket message ID indicating a cpu update.
|
|
65
|
+
*
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
20
68
|
export const WS_ID_CPU_UPDATE = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
*
|
|
72
|
+
* @constant {number}
|
|
73
|
+
*/
|
|
21
74
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
75
|
+
/**
|
|
76
|
+
* Websocket message ID indicating an uptime update.
|
|
77
|
+
*
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
22
80
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
81
|
+
/**
|
|
82
|
+
* Websocket message ID indicating a snackbar message.
|
|
83
|
+
*
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
23
86
|
export const WS_ID_SNACKBAR = 6;
|
|
87
|
+
/**
|
|
88
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
89
|
+
*
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
24
92
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
93
|
+
/**
|
|
94
|
+
* Websocket message ID indicating a state update.
|
|
95
|
+
*
|
|
96
|
+
* @constant {number}
|
|
97
|
+
*/
|
|
25
98
|
export const WS_ID_STATEUPDATE = 8;
|
|
99
|
+
/**
|
|
100
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
101
|
+
*
|
|
102
|
+
* @constant {number}
|
|
103
|
+
*/
|
|
26
104
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
105
|
+
/**
|
|
106
|
+
* Websocket message ID indicating a shelly system update.
|
|
107
|
+
* check:
|
|
108
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
109
|
+
* perform:
|
|
110
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
111
|
+
*
|
|
112
|
+
* @constant {number}
|
|
113
|
+
*/
|
|
27
114
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
115
|
+
/**
|
|
116
|
+
* Websocket message ID indicating a shelly main update.
|
|
117
|
+
* check:
|
|
118
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
119
|
+
* perform:
|
|
120
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
121
|
+
*
|
|
122
|
+
* @constant {number}
|
|
123
|
+
*/
|
|
28
124
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
29
125
|
export class Frontend extends EventEmitter {
|
|
30
126
|
matterbridge;
|
|
@@ -38,7 +134,7 @@ export class Frontend extends EventEmitter {
|
|
|
38
134
|
constructor(matterbridge) {
|
|
39
135
|
super();
|
|
40
136
|
this.matterbridge = matterbridge;
|
|
41
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
137
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
42
138
|
}
|
|
43
139
|
set logLevel(logLevel) {
|
|
44
140
|
this.log.logLevel = logLevel;
|
|
@@ -46,12 +142,41 @@ export class Frontend extends EventEmitter {
|
|
|
46
142
|
async start(port = 8283) {
|
|
47
143
|
this.port = port;
|
|
48
144
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
145
|
+
// Initialize multer with the upload directory
|
|
49
146
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
50
147
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
51
148
|
const upload = multer({ dest: uploadDir });
|
|
149
|
+
// Create the express app that serves the frontend
|
|
52
150
|
this.expressApp = express();
|
|
151
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
152
|
+
/*
|
|
153
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
154
|
+
for (const method of methods) {
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
159
|
+
try {
|
|
160
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
161
|
+
return original(path, ...rest);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
*/
|
|
169
|
+
// Log all requests to the server for debugging
|
|
170
|
+
/*
|
|
171
|
+
this.expressApp.use((req, res, next) => {
|
|
172
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
173
|
+
next();
|
|
174
|
+
});
|
|
175
|
+
*/
|
|
176
|
+
// Serve static files from '/static' endpoint
|
|
53
177
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
54
178
|
if (!hasParameter('ssl')) {
|
|
179
|
+
// Create an HTTP server and attach the express app
|
|
55
180
|
try {
|
|
56
181
|
this.httpServer = createServer(this.expressApp);
|
|
57
182
|
}
|
|
@@ -60,6 +185,7 @@ export class Frontend extends EventEmitter {
|
|
|
60
185
|
this.emit('server_error', error);
|
|
61
186
|
return;
|
|
62
187
|
}
|
|
188
|
+
// Listen on the specified port
|
|
63
189
|
if (hasParameter('ingress')) {
|
|
64
190
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
65
191
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -91,6 +217,7 @@ export class Frontend extends EventEmitter {
|
|
|
91
217
|
});
|
|
92
218
|
}
|
|
93
219
|
else {
|
|
220
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
94
221
|
let cert;
|
|
95
222
|
try {
|
|
96
223
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -120,6 +247,7 @@ export class Frontend extends EventEmitter {
|
|
|
120
247
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
121
248
|
}
|
|
122
249
|
const serverOptions = { cert, key, ca };
|
|
250
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
123
251
|
try {
|
|
124
252
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
125
253
|
}
|
|
@@ -128,6 +256,7 @@ export class Frontend extends EventEmitter {
|
|
|
128
256
|
this.emit('server_error', error);
|
|
129
257
|
return;
|
|
130
258
|
}
|
|
259
|
+
// Listen on the specified port
|
|
131
260
|
if (hasParameter('ingress')) {
|
|
132
261
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
133
262
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -160,16 +289,18 @@ export class Frontend extends EventEmitter {
|
|
|
160
289
|
}
|
|
161
290
|
if (this.initializeError)
|
|
162
291
|
return;
|
|
292
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
163
293
|
const wssPort = this.port;
|
|
164
294
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
165
295
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
166
296
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
167
297
|
const clientIp = request.socket.remoteAddress;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
298
|
+
// Set the global logger callback for the WebSocketServer
|
|
299
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
300
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
301
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
302
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
303
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
173
304
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
174
305
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
175
306
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -204,6 +335,7 @@ export class Frontend extends EventEmitter {
|
|
|
204
335
|
this.webSocketServer.on('error', (ws, error) => {
|
|
205
336
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
206
337
|
});
|
|
338
|
+
// Subscribe to cli events
|
|
207
339
|
cliEmitter.removeAllListeners();
|
|
208
340
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
209
341
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -214,6 +346,8 @@ export class Frontend extends EventEmitter {
|
|
|
214
346
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
215
347
|
this.wssSendCpuUpdate(cpuUsage);
|
|
216
348
|
});
|
|
349
|
+
// Endpoint to validate login code
|
|
350
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
217
351
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
218
352
|
const { password } = req.body;
|
|
219
353
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -232,23 +366,27 @@ export class Frontend extends EventEmitter {
|
|
|
232
366
|
this.log.warn('/api/login error wrong password');
|
|
233
367
|
res.json({ valid: false });
|
|
234
368
|
}
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
235
370
|
}
|
|
236
371
|
catch (error) {
|
|
237
372
|
this.log.error('/api/login error getting password');
|
|
238
373
|
res.json({ valid: false });
|
|
239
374
|
}
|
|
240
375
|
});
|
|
376
|
+
// Endpoint to provide health check for docker
|
|
241
377
|
this.expressApp.get('/health', (req, res) => {
|
|
242
378
|
this.log.debug('Express received /health');
|
|
243
379
|
const healthStatus = {
|
|
244
|
-
status: 'ok',
|
|
245
|
-
uptime: process.uptime(),
|
|
246
|
-
timestamp: new Date().toISOString(),
|
|
380
|
+
status: 'ok', // Indicate service is healthy
|
|
381
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
382
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
247
383
|
};
|
|
248
384
|
res.status(200).json(healthStatus);
|
|
249
385
|
});
|
|
386
|
+
// Endpoint to provide memory usage details
|
|
250
387
|
this.expressApp.get('/memory', async (req, res) => {
|
|
251
388
|
this.log.debug('Express received /memory');
|
|
389
|
+
// Memory usage from process
|
|
252
390
|
const memoryUsageRaw = process.memoryUsage();
|
|
253
391
|
const memoryUsage = {
|
|
254
392
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -257,10 +395,13 @@ export class Frontend extends EventEmitter {
|
|
|
257
395
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
258
396
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
259
397
|
};
|
|
398
|
+
// V8 heap statistics
|
|
260
399
|
const { default: v8 } = await import('node:v8');
|
|
261
400
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
262
401
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
402
|
+
// Format heapStats
|
|
263
403
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
404
|
+
// Format heapSpaces
|
|
264
405
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
265
406
|
...space,
|
|
266
407
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -278,19 +419,23 @@ export class Frontend extends EventEmitter {
|
|
|
278
419
|
};
|
|
279
420
|
res.status(200).json(memoryReport);
|
|
280
421
|
});
|
|
422
|
+
// Endpoint to provide settings
|
|
281
423
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
282
424
|
this.log.debug('The frontend sent /api/settings');
|
|
283
425
|
res.json(await this.getApiSettings());
|
|
284
426
|
});
|
|
427
|
+
// Endpoint to provide plugins
|
|
285
428
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
286
429
|
this.log.debug('The frontend sent /api/plugins');
|
|
287
430
|
res.json(this.getBaseRegisteredPlugins());
|
|
288
431
|
});
|
|
432
|
+
// Endpoint to provide devices
|
|
289
433
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
290
434
|
this.log.debug('The frontend sent /api/devices');
|
|
291
435
|
const devices = await this.getDevices();
|
|
292
436
|
res.json(devices);
|
|
293
437
|
});
|
|
438
|
+
// Endpoint to view the matterbridge log
|
|
294
439
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
295
440
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
296
441
|
try {
|
|
@@ -303,6 +448,7 @@ export class Frontend extends EventEmitter {
|
|
|
303
448
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
304
449
|
}
|
|
305
450
|
});
|
|
451
|
+
// Endpoint to view the matter.js log
|
|
306
452
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
307
453
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
308
454
|
try {
|
|
@@ -315,6 +461,7 @@ export class Frontend extends EventEmitter {
|
|
|
315
461
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
316
462
|
}
|
|
317
463
|
});
|
|
464
|
+
// Endpoint to view the shelly log
|
|
318
465
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
319
466
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
320
467
|
try {
|
|
@@ -327,9 +474,11 @@ export class Frontend extends EventEmitter {
|
|
|
327
474
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
328
475
|
}
|
|
329
476
|
});
|
|
477
|
+
// Endpoint to download the matterbridge log
|
|
330
478
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
331
479
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
332
480
|
try {
|
|
481
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
333
482
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
|
|
334
483
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
|
|
335
484
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
|
|
@@ -340,15 +489,18 @@ export class Frontend extends EventEmitter {
|
|
|
340
489
|
}
|
|
341
490
|
res.type('text/plain');
|
|
342
491
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
492
|
+
/* istanbul ignore if */
|
|
343
493
|
if (error) {
|
|
344
494
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
345
495
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
346
496
|
}
|
|
347
497
|
});
|
|
348
498
|
});
|
|
499
|
+
// Endpoint to download the matter log
|
|
349
500
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
350
501
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
351
502
|
try {
|
|
503
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
352
504
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
353
505
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
354
506
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -359,15 +511,18 @@ export class Frontend extends EventEmitter {
|
|
|
359
511
|
}
|
|
360
512
|
res.type('text/plain');
|
|
361
513
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
514
|
+
/* istanbul ignore if */
|
|
362
515
|
if (error) {
|
|
363
516
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
364
517
|
res.status(500).send('Error downloading the matter log file');
|
|
365
518
|
}
|
|
366
519
|
});
|
|
367
520
|
});
|
|
521
|
+
// Endpoint to download the shelly log
|
|
368
522
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
369
523
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
370
524
|
try {
|
|
525
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
371
526
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
372
527
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
373
528
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -378,74 +533,90 @@ export class Frontend extends EventEmitter {
|
|
|
378
533
|
}
|
|
379
534
|
res.type('text/plain');
|
|
380
535
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
536
|
+
/* istanbul ignore if */
|
|
381
537
|
if (error) {
|
|
382
538
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
383
539
|
res.status(500).send('Error downloading Shelly system log file');
|
|
384
540
|
}
|
|
385
541
|
});
|
|
386
542
|
});
|
|
543
|
+
// Endpoint to download the matterbridge storage directory
|
|
387
544
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
388
545
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
389
546
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
390
547
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
548
|
+
/* istanbul ignore if */
|
|
391
549
|
if (error) {
|
|
392
550
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
393
551
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
394
552
|
}
|
|
395
553
|
});
|
|
396
554
|
});
|
|
555
|
+
// Endpoint to download the matter storage file
|
|
397
556
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
398
557
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
399
558
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
400
559
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
560
|
+
/* istanbul ignore if */
|
|
401
561
|
if (error) {
|
|
402
562
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
403
563
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
404
564
|
}
|
|
405
565
|
});
|
|
406
566
|
});
|
|
567
|
+
// Endpoint to download the matterbridge plugin directory
|
|
407
568
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
408
569
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
409
570
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
410
571
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
572
|
+
/* istanbul ignore if */
|
|
411
573
|
if (error) {
|
|
412
574
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
413
575
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
414
576
|
}
|
|
415
577
|
});
|
|
416
578
|
});
|
|
579
|
+
// Endpoint to download the matterbridge plugin config files
|
|
417
580
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
418
581
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
419
582
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
420
583
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
584
|
+
/* istanbul ignore if */
|
|
421
585
|
if (error) {
|
|
422
586
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
423
587
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
424
588
|
}
|
|
425
589
|
});
|
|
426
590
|
});
|
|
591
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
427
592
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
428
593
|
this.log.debug('The frontend sent /api/download-backup');
|
|
429
594
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
595
|
+
/* istanbul ignore if */
|
|
430
596
|
if (error) {
|
|
431
597
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
432
598
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
433
599
|
}
|
|
434
600
|
});
|
|
435
601
|
});
|
|
602
|
+
// Endpoint to upload a package
|
|
436
603
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
437
604
|
const { filename } = req.body;
|
|
438
605
|
const file = req.file;
|
|
606
|
+
/* istanbul ignore if */
|
|
439
607
|
if (!file || !filename) {
|
|
440
608
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
441
609
|
res.status(400).send('Invalid request: file and filename are required');
|
|
442
610
|
return;
|
|
443
611
|
}
|
|
444
612
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
613
|
+
// Define the path where the plugin file will be saved
|
|
445
614
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
446
615
|
try {
|
|
616
|
+
// Move the uploaded file to the specified path
|
|
447
617
|
await fs.rename(file.path, filePath);
|
|
448
618
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
619
|
+
// Install the plugin package
|
|
449
620
|
if (filename.endsWith('.tgz')) {
|
|
450
621
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
451
622
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -465,6 +636,7 @@ export class Frontend extends EventEmitter {
|
|
|
465
636
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
466
637
|
}
|
|
467
638
|
});
|
|
639
|
+
// Fallback for routing (must be the last route)
|
|
468
640
|
this.expressApp.use((req, res) => {
|
|
469
641
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
470
642
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -473,13 +645,16 @@ export class Frontend extends EventEmitter {
|
|
|
473
645
|
}
|
|
474
646
|
async stop() {
|
|
475
647
|
this.log.debug('Stopping the frontend...');
|
|
648
|
+
// Remove listeners from the express app
|
|
476
649
|
if (this.expressApp) {
|
|
477
650
|
this.expressApp.removeAllListeners();
|
|
478
651
|
this.expressApp = undefined;
|
|
479
652
|
this.log.debug('Frontend app closed successfully');
|
|
480
653
|
}
|
|
654
|
+
// Close the WebSocket server
|
|
481
655
|
if (this.webSocketServer) {
|
|
482
656
|
this.log.debug('Closing WebSocket server...');
|
|
657
|
+
// Close all active connections
|
|
483
658
|
this.webSocketServer.clients.forEach((client) => {
|
|
484
659
|
if (client.readyState === WebSocket.OPEN) {
|
|
485
660
|
client.close();
|
|
@@ -499,6 +674,7 @@ export class Frontend extends EventEmitter {
|
|
|
499
674
|
this.webSocketServer.removeAllListeners();
|
|
500
675
|
this.webSocketServer = undefined;
|
|
501
676
|
}
|
|
677
|
+
// Close the http server
|
|
502
678
|
if (this.httpServer) {
|
|
503
679
|
this.log.debug('Closing http server...');
|
|
504
680
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -516,6 +692,7 @@ export class Frontend extends EventEmitter {
|
|
|
516
692
|
this.httpServer = undefined;
|
|
517
693
|
this.log.debug('Frontend http server closed successfully');
|
|
518
694
|
}
|
|
695
|
+
// Close the https server
|
|
519
696
|
if (this.httpsServer) {
|
|
520
697
|
this.log.debug('Closing https server...');
|
|
521
698
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -535,6 +712,7 @@ export class Frontend extends EventEmitter {
|
|
|
535
712
|
}
|
|
536
713
|
this.log.debug('Frontend stopped successfully');
|
|
537
714
|
}
|
|
715
|
+
// Function to format bytes to KB, MB, or GB
|
|
538
716
|
formatMemoryUsage = (bytes) => {
|
|
539
717
|
if (bytes >= 1024 ** 3) {
|
|
540
718
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -546,6 +724,7 @@ export class Frontend extends EventEmitter {
|
|
|
546
724
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
547
725
|
}
|
|
548
726
|
};
|
|
727
|
+
// Function to format system uptime with only the most significant unit
|
|
549
728
|
formatOsUpTime = (seconds) => {
|
|
550
729
|
if (seconds >= 86400) {
|
|
551
730
|
const days = Math.floor(seconds / 86400);
|
|
@@ -561,7 +740,13 @@ export class Frontend extends EventEmitter {
|
|
|
561
740
|
}
|
|
562
741
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
563
742
|
};
|
|
743
|
+
/**
|
|
744
|
+
* Retrieves the api settings data.
|
|
745
|
+
*
|
|
746
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
747
|
+
*/
|
|
564
748
|
async getApiSettings() {
|
|
749
|
+
// Update the system information
|
|
565
750
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
566
751
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
567
752
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -570,6 +755,7 @@ export class Frontend extends EventEmitter {
|
|
|
570
755
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
571
756
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
572
757
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
758
|
+
// Update the matterbridge information
|
|
573
759
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
574
760
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
575
761
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -588,6 +774,12 @@ export class Frontend extends EventEmitter {
|
|
|
588
774
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
589
775
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
590
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Retrieves the reachable attribute.
|
|
779
|
+
*
|
|
780
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
781
|
+
* @returns {boolean} The reachable attribute.
|
|
782
|
+
*/
|
|
591
783
|
getReachability(device) {
|
|
592
784
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
593
785
|
return false;
|
|
@@ -599,6 +791,12 @@ export class Frontend extends EventEmitter {
|
|
|
599
791
|
return true;
|
|
600
792
|
return false;
|
|
601
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Retrieves the power source attribute.
|
|
796
|
+
*
|
|
797
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
798
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
799
|
+
*/
|
|
602
800
|
getPowerSource(endpoint) {
|
|
603
801
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
604
802
|
return undefined;
|
|
@@ -614,13 +812,21 @@ export class Frontend extends EventEmitter {
|
|
|
614
812
|
}
|
|
615
813
|
return;
|
|
616
814
|
};
|
|
815
|
+
// Root endpoint
|
|
617
816
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
618
817
|
return powerSource(endpoint);
|
|
818
|
+
// Child endpoints
|
|
619
819
|
for (const child of endpoint.getChildEndpoints()) {
|
|
620
820
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
621
821
|
return powerSource(child);
|
|
622
822
|
}
|
|
623
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device.
|
|
826
|
+
*
|
|
827
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
828
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
829
|
+
*/
|
|
624
830
|
getMatterDataFromDevice(device) {
|
|
625
831
|
if (device.mode === 'server' && device.serverNode) {
|
|
626
832
|
return {
|
|
@@ -632,6 +838,13 @@ export class Frontend extends EventEmitter {
|
|
|
632
838
|
};
|
|
633
839
|
}
|
|
634
840
|
}
|
|
841
|
+
/**
|
|
842
|
+
* Retrieves the cluster text description from a given device.
|
|
843
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
844
|
+
*
|
|
845
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
846
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
847
|
+
*/
|
|
635
848
|
getClusterTextFromDevice(device) {
|
|
636
849
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
637
850
|
return '';
|
|
@@ -656,6 +869,7 @@ export class Frontend extends EventEmitter {
|
|
|
656
869
|
let attributes = '';
|
|
657
870
|
let supportedModes = [];
|
|
658
871
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
872
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
659
873
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
660
874
|
return;
|
|
661
875
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -745,8 +959,14 @@ export class Frontend extends EventEmitter {
|
|
|
745
959
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
746
960
|
attributes += `${getUserLabel(device)} `;
|
|
747
961
|
});
|
|
962
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
748
963
|
return attributes.trimStart().trimEnd();
|
|
749
964
|
}
|
|
965
|
+
/**
|
|
966
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
967
|
+
*
|
|
968
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
969
|
+
*/
|
|
750
970
|
getBaseRegisteredPlugins() {
|
|
751
971
|
const baseRegisteredPlugins = [];
|
|
752
972
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -785,11 +1005,19 @@ export class Frontend extends EventEmitter {
|
|
|
785
1005
|
}
|
|
786
1006
|
return baseRegisteredPlugins;
|
|
787
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Retrieves the devices from Matterbridge.
|
|
1010
|
+
*
|
|
1011
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1012
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1013
|
+
*/
|
|
788
1014
|
async getDevices(pluginName) {
|
|
789
1015
|
const devices = [];
|
|
790
1016
|
for (const device of this.matterbridge.devices.array()) {
|
|
1017
|
+
// Filter by pluginName if provided
|
|
791
1018
|
if (pluginName && pluginName !== device.plugin)
|
|
792
1019
|
continue;
|
|
1020
|
+
// Check if the device has the required properties
|
|
793
1021
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
794
1022
|
continue;
|
|
795
1023
|
devices.push({
|
|
@@ -809,22 +1037,37 @@ export class Frontend extends EventEmitter {
|
|
|
809
1037
|
}
|
|
810
1038
|
return devices;
|
|
811
1039
|
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1042
|
+
*
|
|
1043
|
+
* Response for /api/clusters
|
|
1044
|
+
*
|
|
1045
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1046
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1047
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1048
|
+
*/
|
|
812
1049
|
getClusters(pluginName, endpointNumber) {
|
|
813
1050
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
814
1051
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
815
1052
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
816
1053
|
return;
|
|
817
1054
|
}
|
|
1055
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1056
|
+
// Get the device types from the main endpoint
|
|
818
1057
|
const deviceTypes = [];
|
|
819
1058
|
const clusters = [];
|
|
820
1059
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
821
1060
|
deviceTypes.push(d.deviceType);
|
|
822
1061
|
});
|
|
1062
|
+
// Get the clusters from the main endpoint
|
|
823
1063
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
824
1064
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
825
1065
|
return;
|
|
826
1066
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
827
1067
|
return;
|
|
1068
|
+
// console.log(
|
|
1069
|
+
// `${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}`,
|
|
1070
|
+
// );
|
|
828
1071
|
clusters.push({
|
|
829
1072
|
endpoint: endpoint.number.toString(),
|
|
830
1073
|
id: 'main',
|
|
@@ -837,12 +1080,18 @@ export class Frontend extends EventEmitter {
|
|
|
837
1080
|
attributeLocalValue: attributeValue,
|
|
838
1081
|
});
|
|
839
1082
|
});
|
|
1083
|
+
// Get the child endpoints
|
|
840
1084
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1085
|
+
// if (childEndpoints.length === 0) {
|
|
1086
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1087
|
+
// }
|
|
841
1088
|
childEndpoints.forEach((childEndpoint) => {
|
|
842
1089
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
843
1090
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
844
1091
|
return;
|
|
845
1092
|
}
|
|
1093
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1094
|
+
// Get the device types of the child endpoint
|
|
846
1095
|
const deviceTypes = [];
|
|
847
1096
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
848
1097
|
deviceTypes.push(d.deviceType);
|
|
@@ -852,9 +1101,12 @@ export class Frontend extends EventEmitter {
|
|
|
852
1101
|
return;
|
|
853
1102
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
854
1103
|
return;
|
|
1104
|
+
// console.log(
|
|
1105
|
+
// `${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}`,
|
|
1106
|
+
// );
|
|
855
1107
|
clusters.push({
|
|
856
1108
|
endpoint: childEndpoint.number.toString(),
|
|
857
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1109
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
858
1110
|
deviceTypes,
|
|
859
1111
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
860
1112
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -867,6 +1119,13 @@ export class Frontend extends EventEmitter {
|
|
|
867
1119
|
});
|
|
868
1120
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
869
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1124
|
+
*
|
|
1125
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1126
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1127
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1128
|
+
*/
|
|
870
1129
|
async wsMessageHandler(client, message) {
|
|
871
1130
|
let data;
|
|
872
1131
|
try {
|
|
@@ -913,32 +1172,41 @@ export class Frontend extends EventEmitter {
|
|
|
913
1172
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
914
1173
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
915
1174
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1175
|
+
// The install comes from InstallPlugins
|
|
916
1176
|
this.matterbridge.plugins
|
|
917
1177
|
.add(packageName)
|
|
918
1178
|
.then((plugin) => {
|
|
919
1179
|
if (plugin) {
|
|
1180
|
+
// The plugin is not registered
|
|
920
1181
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
921
1182
|
this.matterbridge.plugins
|
|
922
1183
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1184
|
+
// eslint-disable-next-line promise/no-nesting
|
|
923
1185
|
.then(() => {
|
|
924
1186
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
925
1187
|
this.wssSendRefreshRequired('plugins');
|
|
926
1188
|
return;
|
|
927
1189
|
})
|
|
1190
|
+
// eslint-disable-next-line promise/no-nesting
|
|
928
1191
|
.catch((_error) => {
|
|
1192
|
+
//
|
|
929
1193
|
});
|
|
930
1194
|
}
|
|
931
1195
|
else {
|
|
1196
|
+
// The plugin is already registered
|
|
932
1197
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
933
1198
|
this.wssSendRefreshRequired('plugins');
|
|
934
1199
|
this.wssSendRestartRequired();
|
|
935
1200
|
}
|
|
936
1201
|
return;
|
|
937
1202
|
})
|
|
1203
|
+
// eslint-disable-next-line promise/no-nesting
|
|
938
1204
|
.catch((_error) => {
|
|
1205
|
+
//
|
|
939
1206
|
});
|
|
940
1207
|
}
|
|
941
1208
|
else {
|
|
1209
|
+
// The package is matterbridge
|
|
942
1210
|
if (this.matterbridge.restartMode !== '') {
|
|
943
1211
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
944
1212
|
this.matterbridge.shutdownProcess();
|
|
@@ -961,6 +1229,7 @@ export class Frontend extends EventEmitter {
|
|
|
961
1229
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
962
1230
|
return;
|
|
963
1231
|
}
|
|
1232
|
+
// The package is a plugin
|
|
964
1233
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
965
1234
|
if (plugin) {
|
|
966
1235
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -969,6 +1238,7 @@ export class Frontend extends EventEmitter {
|
|
|
969
1238
|
this.wssSendRefreshRequired('plugins');
|
|
970
1239
|
this.wssSendRefreshRequired('devices');
|
|
971
1240
|
}
|
|
1241
|
+
// Uninstall the package
|
|
972
1242
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
973
1243
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
974
1244
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1009,6 +1279,7 @@ export class Frontend extends EventEmitter {
|
|
|
1009
1279
|
return;
|
|
1010
1280
|
})
|
|
1011
1281
|
.catch((_error) => {
|
|
1282
|
+
//
|
|
1012
1283
|
});
|
|
1013
1284
|
}
|
|
1014
1285
|
else {
|
|
@@ -1055,6 +1326,7 @@ export class Frontend extends EventEmitter {
|
|
|
1055
1326
|
return;
|
|
1056
1327
|
})
|
|
1057
1328
|
.catch((_error) => {
|
|
1329
|
+
//
|
|
1058
1330
|
});
|
|
1059
1331
|
}
|
|
1060
1332
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1289,22 +1561,22 @@ export class Frontend extends EventEmitter {
|
|
|
1289
1561
|
if (isValidString(data.params.value, 4)) {
|
|
1290
1562
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1291
1563
|
if (data.params.value === 'Debug') {
|
|
1292
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1564
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1293
1565
|
}
|
|
1294
1566
|
else if (data.params.value === 'Info') {
|
|
1295
|
-
await this.matterbridge.setLogLevel("info");
|
|
1567
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1296
1568
|
}
|
|
1297
1569
|
else if (data.params.value === 'Notice') {
|
|
1298
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1570
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1299
1571
|
}
|
|
1300
1572
|
else if (data.params.value === 'Warn') {
|
|
1301
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1573
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1302
1574
|
}
|
|
1303
1575
|
else if (data.params.value === 'Error') {
|
|
1304
|
-
await this.matterbridge.setLogLevel("error");
|
|
1576
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1305
1577
|
}
|
|
1306
1578
|
else if (data.params.value === 'Fatal') {
|
|
1307
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1579
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1308
1580
|
}
|
|
1309
1581
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1310
1582
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1315,6 +1587,7 @@ export class Frontend extends EventEmitter {
|
|
|
1315
1587
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1316
1588
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1317
1589
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1590
|
+
// Create the file logger for matterbridge
|
|
1318
1591
|
if (data.params.value)
|
|
1319
1592
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1320
1593
|
else
|
|
@@ -1361,6 +1634,7 @@ export class Frontend extends EventEmitter {
|
|
|
1361
1634
|
});
|
|
1362
1635
|
}
|
|
1363
1636
|
catch (error) {
|
|
1637
|
+
/* istanbul ignore next */
|
|
1364
1638
|
this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1365
1639
|
}
|
|
1366
1640
|
}
|
|
@@ -1369,6 +1643,7 @@ export class Frontend extends EventEmitter {
|
|
|
1369
1643
|
Logger.removeLogger('matterfilelogger');
|
|
1370
1644
|
}
|
|
1371
1645
|
catch (error) {
|
|
1646
|
+
/* istanbul ignore next */
|
|
1372
1647
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1373
1648
|
}
|
|
1374
1649
|
}
|
|
@@ -1481,15 +1756,19 @@ export class Frontend extends EventEmitter {
|
|
|
1481
1756
|
return;
|
|
1482
1757
|
}
|
|
1483
1758
|
const config = plugin.configJson;
|
|
1759
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1484
1760
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1761
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1485
1762
|
if (select === 'serial')
|
|
1486
1763
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1487
1764
|
if (select === 'name')
|
|
1488
1765
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1489
1766
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1767
|
+
// Remove postfix from the serial if it exists
|
|
1490
1768
|
if (config.postfix) {
|
|
1491
1769
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1492
1770
|
}
|
|
1771
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1493
1772
|
if (isValidArray(config.whiteList, 1)) {
|
|
1494
1773
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1495
1774
|
config.whiteList.push(data.params.serial);
|
|
@@ -1498,6 +1777,7 @@ export class Frontend extends EventEmitter {
|
|
|
1498
1777
|
config.whiteList.push(data.params.name);
|
|
1499
1778
|
}
|
|
1500
1779
|
}
|
|
1780
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1501
1781
|
if (isValidArray(config.blackList, 1)) {
|
|
1502
1782
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1503
1783
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1528,7 +1808,9 @@ export class Frontend extends EventEmitter {
|
|
|
1528
1808
|
return;
|
|
1529
1809
|
}
|
|
1530
1810
|
const config = plugin.configJson;
|
|
1811
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1531
1812
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1813
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1532
1814
|
if (select === 'serial')
|
|
1533
1815
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1534
1816
|
if (select === 'name')
|
|
@@ -1537,6 +1819,7 @@ export class Frontend extends EventEmitter {
|
|
|
1537
1819
|
if (config.postfix) {
|
|
1538
1820
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1539
1821
|
}
|
|
1822
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1540
1823
|
if (isValidArray(config.whiteList, 1)) {
|
|
1541
1824
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1542
1825
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1545,6 +1828,7 @@ export class Frontend extends EventEmitter {
|
|
|
1545
1828
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1546
1829
|
}
|
|
1547
1830
|
}
|
|
1831
|
+
// Add the serial to the blackList
|
|
1548
1832
|
if (isValidArray(config.blackList)) {
|
|
1549
1833
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1550
1834
|
config.blackList.push(data.params.serial);
|
|
@@ -1578,114 +1862,230 @@ export class Frontend extends EventEmitter {
|
|
|
1578
1862
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1579
1863
|
}
|
|
1580
1864
|
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1867
|
+
*
|
|
1868
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1869
|
+
* @param {string} time - The time string of the message
|
|
1870
|
+
* @param {string} name - The logger name of the message
|
|
1871
|
+
* @param {string} message - The content of the message.
|
|
1872
|
+
*
|
|
1873
|
+
* @remarks
|
|
1874
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1875
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1876
|
+
* The function sends the message to all connected clients.
|
|
1877
|
+
*/
|
|
1581
1878
|
wssSendMessage(level, time, name, message) {
|
|
1582
1879
|
if (!level || !time || !name || !message)
|
|
1583
1880
|
return;
|
|
1881
|
+
// Remove ANSI escape codes from the message
|
|
1882
|
+
// eslint-disable-next-line no-control-regex
|
|
1584
1883
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1884
|
+
// Remove leading asterisks from the message
|
|
1585
1885
|
message = message.replace(/^\*+/, '');
|
|
1886
|
+
// Replace all occurrences of \t and \n
|
|
1586
1887
|
message = message.replace(/[\t\n]/g, '');
|
|
1888
|
+
// Remove non-printable characters
|
|
1889
|
+
// eslint-disable-next-line no-control-regex
|
|
1587
1890
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1891
|
+
// Replace all occurrences of \" with "
|
|
1588
1892
|
message = message.replace(/\\"/g, '"');
|
|
1893
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1589
1894
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1895
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1590
1896
|
const maxContinuousLength = 100;
|
|
1591
1897
|
const keepStartLength = 20;
|
|
1592
1898
|
const keepEndLength = 20;
|
|
1899
|
+
// Split the message into words
|
|
1593
1900
|
message = message
|
|
1594
1901
|
.split(' ')
|
|
1595
1902
|
.map((word) => {
|
|
1903
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1596
1904
|
if (word.length > maxContinuousLength) {
|
|
1597
1905
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1598
1906
|
}
|
|
1599
1907
|
return word;
|
|
1600
1908
|
})
|
|
1601
1909
|
.join(' ');
|
|
1910
|
+
// Send the message to all connected clients
|
|
1602
1911
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1603
1912
|
if (client.readyState === WebSocket.OPEN) {
|
|
1604
1913
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1605
1914
|
}
|
|
1606
1915
|
});
|
|
1607
1916
|
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1919
|
+
*
|
|
1920
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1921
|
+
* possible values:
|
|
1922
|
+
* - 'matterbridgeLatestVersion'
|
|
1923
|
+
* - 'matterbridgeAdvertise'
|
|
1924
|
+
* - 'online'
|
|
1925
|
+
* - 'offline'
|
|
1926
|
+
* - 'reachability'
|
|
1927
|
+
* - 'settings'
|
|
1928
|
+
* - 'plugins'
|
|
1929
|
+
* - 'pluginsRestart'
|
|
1930
|
+
* - 'devices'
|
|
1931
|
+
* - 'fabrics'
|
|
1932
|
+
* - 'sessions'
|
|
1933
|
+
*/
|
|
1608
1934
|
wssSendRefreshRequired(changed = null) {
|
|
1609
1935
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1936
|
+
// Send the message to all connected clients
|
|
1610
1937
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1611
1938
|
if (client.readyState === WebSocket.OPEN) {
|
|
1612
1939
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1613
1940
|
}
|
|
1614
1941
|
});
|
|
1615
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1945
|
+
*
|
|
1946
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1947
|
+
*/
|
|
1616
1948
|
wssSendRestartRequired(snackbar = true) {
|
|
1617
1949
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1618
1950
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1619
1951
|
if (snackbar === true)
|
|
1620
1952
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1953
|
+
// Send the message to all connected clients
|
|
1621
1954
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1622
1955
|
if (client.readyState === WebSocket.OPEN) {
|
|
1623
1956
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1624
1957
|
}
|
|
1625
1958
|
});
|
|
1626
1959
|
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1962
|
+
*
|
|
1963
|
+
*/
|
|
1627
1964
|
wssSendUpdateRequired() {
|
|
1628
1965
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1629
1966
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1967
|
+
// Send the message to all connected clients
|
|
1630
1968
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1631
1969
|
if (client.readyState === WebSocket.OPEN) {
|
|
1632
1970
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1633
1971
|
}
|
|
1634
1972
|
});
|
|
1635
1973
|
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Sends a cpu update message to all connected clients.
|
|
1976
|
+
*
|
|
1977
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
1978
|
+
*/
|
|
1636
1979
|
wssSendCpuUpdate(cpuUsage) {
|
|
1637
1980
|
if (hasParameter('debug'))
|
|
1638
1981
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1982
|
+
// Send the message to all connected clients
|
|
1639
1983
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1640
1984
|
if (client.readyState === WebSocket.OPEN) {
|
|
1641
1985
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1642
1986
|
}
|
|
1643
1987
|
});
|
|
1644
1988
|
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Sends a memory update message to all connected clients.
|
|
1991
|
+
*
|
|
1992
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
1993
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
1994
|
+
* @param {string} rss - The resident set size in bytes.
|
|
1995
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
1996
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
1997
|
+
* @param {string} external - The external memory in bytes.
|
|
1998
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
1999
|
+
*/
|
|
1645
2000
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1646
2001
|
if (hasParameter('debug'))
|
|
1647
2002
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2003
|
+
// Send the message to all connected clients
|
|
1648
2004
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1649
2005
|
if (client.readyState === WebSocket.OPEN) {
|
|
1650
2006
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1651
2007
|
}
|
|
1652
2008
|
});
|
|
1653
2009
|
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Sends an uptime update message to all connected clients.
|
|
2012
|
+
*
|
|
2013
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2014
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2015
|
+
*/
|
|
1654
2016
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1655
2017
|
if (hasParameter('debug'))
|
|
1656
2018
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2019
|
+
// Send the message to all connected clients
|
|
1657
2020
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1658
2021
|
if (client.readyState === WebSocket.OPEN) {
|
|
1659
2022
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1660
2023
|
}
|
|
1661
2024
|
});
|
|
1662
2025
|
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Sends an open snackbar message to all connected clients.
|
|
2028
|
+
*
|
|
2029
|
+
* @param {string} message - The message to send.
|
|
2030
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2031
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2032
|
+
*/
|
|
1663
2033
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1664
2034
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2035
|
+
// Send the message to all connected clients
|
|
1665
2036
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1666
2037
|
if (client.readyState === WebSocket.OPEN) {
|
|
1667
2038
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1668
2039
|
}
|
|
1669
2040
|
});
|
|
1670
2041
|
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Sends a close snackbar message to all connected clients.
|
|
2044
|
+
*
|
|
2045
|
+
* @param {string} message - The message to send.
|
|
2046
|
+
*/
|
|
1671
2047
|
wssSendCloseSnackbarMessage(message) {
|
|
1672
2048
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2049
|
+
// Send the message to all connected clients
|
|
1673
2050
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1674
2051
|
if (client.readyState === WebSocket.OPEN) {
|
|
1675
2052
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1676
2053
|
}
|
|
1677
2054
|
});
|
|
1678
2055
|
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2058
|
+
*
|
|
2059
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2060
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2061
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2062
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2063
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2064
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2065
|
+
*
|
|
2066
|
+
* @remarks
|
|
2067
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2068
|
+
* with the updated attribute information.
|
|
2069
|
+
*/
|
|
1679
2070
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1680
2071
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2072
|
+
// Send the message to all connected clients
|
|
1681
2073
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1682
2074
|
if (client.readyState === WebSocket.OPEN) {
|
|
1683
2075
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1684
2076
|
}
|
|
1685
2077
|
});
|
|
1686
2078
|
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Sends a message to all connected clients.
|
|
2081
|
+
*
|
|
2082
|
+
* @param {number} id - The message id.
|
|
2083
|
+
* @param {string} method - The message method.
|
|
2084
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2085
|
+
*/
|
|
1687
2086
|
wssBroadcastMessage(id, method, params) {
|
|
1688
2087
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2088
|
+
// Send the message to all connected clients
|
|
1689
2089
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1690
2090
|
if (client.readyState === WebSocket.OPEN) {
|
|
1691
2091
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1693,3 +2093,4 @@ export class Frontend extends EventEmitter {
|
|
|
1693
2093
|
});
|
|
1694
2094
|
}
|
|
1695
2095
|
}
|
|
2096
|
+
//# sourceMappingURL=frontend.js.map
|