matterbridge 3.0.5-dev-20250607-33ce8f8 → 3.0.5
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 +1 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -5
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/evse.d.ts +63 -0
- package/dist/evse.d.ts.map +1 -0
- package/dist/evse.js +66 -29
- package/dist/evse.js.map +1 -0
- package/dist/frontend.d.ts +256 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +374 -16
- package/dist/frontend.js.map +1 -0
- package/dist/helpers.d.ts +47 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +51 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -0
- package/dist/laundryWasher.d.ts +243 -0
- package/dist/laundryWasher.d.ts.map +1 -0
- package/dist/laundryWasher.js +92 -7
- package/dist/laundryWasher.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 +2 -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 +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +449 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +747 -47
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +34 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1379 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +49 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +644 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +578 -15
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +34 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1079 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +924 -24
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2749 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +172 -10
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +294 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +225 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +196 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +273 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +264 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/roboticVacuumCleaner.d.ts +102 -0
- package/dist/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/roboticVacuumCleaner.js +81 -6
- package/dist/roboticVacuumCleaner.js.map +1 -0
- package/dist/shelly.d.ts +153 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +155 -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 +58 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +53 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +58 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +53 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +32 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +42 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +38 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +71 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +11 -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 +48 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +57 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +102 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +100 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +69 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +76 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/wait.d.ts +52 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +58 -9
- package/dist/utils/wait.js.map +1 -0
- package/dist/waterHeater.d.ts +90 -0
- package/dist/waterHeater.d.ts.map +1 -0
- package/dist/waterHeater.js +62 -2
- package/dist/waterHeater.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,29 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Frontend.
|
|
3
|
+
*
|
|
4
|
+
* @file frontend.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2025-01-13
|
|
7
|
+
* @version 1.0.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// @matter
|
|
1
24
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
2
25
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
26
|
+
// Node modules
|
|
3
27
|
import { createServer } from 'node:http';
|
|
4
28
|
import https from 'node:https';
|
|
5
29
|
import os from 'node:os';
|
|
6
30
|
import path from 'node:path';
|
|
7
31
|
import { promises as fs } from 'node:fs';
|
|
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, wr, YELLOW, nt } from './logger/export.js';
|
|
38
|
+
// Matterbridge
|
|
12
39
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout } from './utils/export.js';
|
|
13
40
|
import { plg } from './matterbridgeTypes.js';
|
|
14
41
|
import { hasParameter } from './utils/export.js';
|
|
15
42
|
import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
|
|
43
|
+
/**
|
|
44
|
+
* Websocket message ID for logging.
|
|
45
|
+
* @constant {number}
|
|
46
|
+
*/
|
|
16
47
|
export const WS_ID_LOG = 0;
|
|
48
|
+
/**
|
|
49
|
+
* Websocket message ID indicating a refresh is needed.
|
|
50
|
+
* @constant {number}
|
|
51
|
+
*/
|
|
17
52
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
53
|
+
/**
|
|
54
|
+
* Websocket message ID indicating a restart is needed.
|
|
55
|
+
* @constant {number}
|
|
56
|
+
*/
|
|
18
57
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
58
|
+
/**
|
|
59
|
+
* Websocket message ID indicating a cpu update.
|
|
60
|
+
* @constant {number}
|
|
61
|
+
*/
|
|
19
62
|
export const WS_ID_CPU_UPDATE = 3;
|
|
63
|
+
/**
|
|
64
|
+
* Websocket message ID indicating a memory update.
|
|
65
|
+
* @constant {number}
|
|
66
|
+
*/
|
|
20
67
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
68
|
+
/**
|
|
69
|
+
* Websocket message ID indicating an uptime update.
|
|
70
|
+
* @constant {number}
|
|
71
|
+
*/
|
|
21
72
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
73
|
+
/**
|
|
74
|
+
* Websocket message ID indicating a snackbar message.
|
|
75
|
+
* @constant {number}
|
|
76
|
+
*/
|
|
22
77
|
export const WS_ID_SNACKBAR = 6;
|
|
78
|
+
/**
|
|
79
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
80
|
+
* @constant {number}
|
|
81
|
+
*/
|
|
23
82
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
83
|
+
/**
|
|
84
|
+
* Websocket message ID indicating a state update.
|
|
85
|
+
* @constant {number}
|
|
86
|
+
*/
|
|
24
87
|
export const WS_ID_STATEUPDATE = 8;
|
|
88
|
+
/**
|
|
89
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
25
92
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
93
|
+
/**
|
|
94
|
+
* Websocket message ID indicating a shelly system update.
|
|
95
|
+
* check:
|
|
96
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
97
|
+
* perform:
|
|
98
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
99
|
+
* @constant {number}
|
|
100
|
+
*/
|
|
26
101
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
102
|
+
/**
|
|
103
|
+
* Websocket message ID indicating a shelly main update.
|
|
104
|
+
* check:
|
|
105
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
106
|
+
* perform:
|
|
107
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
108
|
+
* @constant {number}
|
|
109
|
+
*/
|
|
27
110
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
28
111
|
export class Frontend {
|
|
29
112
|
matterbridge;
|
|
@@ -36,7 +119,7 @@ export class Frontend {
|
|
|
36
119
|
webSocketServer;
|
|
37
120
|
constructor(matterbridge) {
|
|
38
121
|
this.matterbridge = matterbridge;
|
|
39
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
122
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
40
123
|
}
|
|
41
124
|
set logLevel(logLevel) {
|
|
42
125
|
this.log.logLevel = logLevel;
|
|
@@ -44,13 +127,43 @@ export class Frontend {
|
|
|
44
127
|
async start(port = 8283) {
|
|
45
128
|
this.port = port;
|
|
46
129
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
130
|
+
// Initialize multer with the upload directory
|
|
47
131
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
48
132
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
49
133
|
const upload = multer({ dest: uploadDir });
|
|
134
|
+
// Create the express app that serves the frontend
|
|
50
135
|
this.expressApp = express();
|
|
136
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
137
|
+
/*
|
|
138
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
139
|
+
for (const method of methods) {
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
144
|
+
try {
|
|
145
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
146
|
+
return original(path, ...rest);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
*/
|
|
154
|
+
// Log all requests to the server for debugging
|
|
155
|
+
/*
|
|
156
|
+
this.expressApp.use((req, res, next) => {
|
|
157
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
158
|
+
next();
|
|
159
|
+
});
|
|
160
|
+
*/
|
|
161
|
+
// Serve static files from '/static' endpoint
|
|
51
162
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
52
163
|
if (!hasParameter('ssl')) {
|
|
164
|
+
// Create an HTTP server and attach the express app
|
|
53
165
|
this.httpServer = createServer(this.expressApp);
|
|
166
|
+
// Listen on the specified port
|
|
54
167
|
if (hasParameter('ingress')) {
|
|
55
168
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
56
169
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -64,6 +177,7 @@ export class Frontend {
|
|
|
64
177
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
65
178
|
});
|
|
66
179
|
}
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
181
|
this.httpServer.on('error', (error) => {
|
|
68
182
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
69
183
|
switch (error.code) {
|
|
@@ -79,6 +193,7 @@ export class Frontend {
|
|
|
79
193
|
});
|
|
80
194
|
}
|
|
81
195
|
else {
|
|
196
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
82
197
|
let cert;
|
|
83
198
|
try {
|
|
84
199
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -106,7 +221,9 @@ export class Frontend {
|
|
|
106
221
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
107
222
|
}
|
|
108
223
|
const serverOptions = { cert, key, ca };
|
|
224
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
109
225
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
226
|
+
// Listen on the specified port
|
|
110
227
|
if (hasParameter('ingress')) {
|
|
111
228
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
112
229
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -120,6 +237,7 @@ export class Frontend {
|
|
|
120
237
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
121
238
|
});
|
|
122
239
|
}
|
|
240
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
241
|
this.httpsServer.on('error', (error) => {
|
|
124
242
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
125
243
|
switch (error.code) {
|
|
@@ -136,16 +254,18 @@ export class Frontend {
|
|
|
136
254
|
}
|
|
137
255
|
if (this.initializeError)
|
|
138
256
|
return;
|
|
257
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
139
258
|
const wssPort = this.port;
|
|
140
259
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
141
260
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
142
261
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
143
262
|
const clientIp = request.socket.remoteAddress;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
263
|
+
// Set the global logger callback for the WebSocketServer
|
|
264
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
265
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
266
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
267
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
268
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
149
269
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
150
270
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
151
271
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -179,6 +299,7 @@ export class Frontend {
|
|
|
179
299
|
this.webSocketServer.on('error', (ws, error) => {
|
|
180
300
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
181
301
|
});
|
|
302
|
+
// Subscribe to cli events
|
|
182
303
|
const { cliEmitter } = await import('./cli.js');
|
|
183
304
|
cliEmitter.removeAllListeners();
|
|
184
305
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
@@ -190,6 +311,8 @@ export class Frontend {
|
|
|
190
311
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
191
312
|
this.wssSendCpuUpdate(cpuUsage);
|
|
192
313
|
});
|
|
314
|
+
// Endpoint to validate login code
|
|
315
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
193
316
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
194
317
|
const { password } = req.body;
|
|
195
318
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -208,23 +331,27 @@ export class Frontend {
|
|
|
208
331
|
this.log.warn('/api/login error wrong password');
|
|
209
332
|
res.json({ valid: false });
|
|
210
333
|
}
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
211
335
|
}
|
|
212
336
|
catch (error) {
|
|
213
337
|
this.log.error('/api/login error getting password');
|
|
214
338
|
res.json({ valid: false });
|
|
215
339
|
}
|
|
216
340
|
});
|
|
341
|
+
// Endpoint to provide health check for docker
|
|
217
342
|
this.expressApp.get('/health', (req, res) => {
|
|
218
343
|
this.log.debug('Express received /health');
|
|
219
344
|
const healthStatus = {
|
|
220
|
-
status: 'ok',
|
|
221
|
-
uptime: process.uptime(),
|
|
222
|
-
timestamp: new Date().toISOString(),
|
|
345
|
+
status: 'ok', // Indicate service is healthy
|
|
346
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
347
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
223
348
|
};
|
|
224
349
|
res.status(200).json(healthStatus);
|
|
225
350
|
});
|
|
351
|
+
// Endpoint to provide memory usage details
|
|
226
352
|
this.expressApp.get('/memory', async (req, res) => {
|
|
227
353
|
this.log.debug('Express received /memory');
|
|
354
|
+
// Memory usage from process
|
|
228
355
|
const memoryUsageRaw = process.memoryUsage();
|
|
229
356
|
const memoryUsage = {
|
|
230
357
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -233,10 +360,13 @@ export class Frontend {
|
|
|
233
360
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
234
361
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
235
362
|
};
|
|
363
|
+
// V8 heap statistics
|
|
236
364
|
const { default: v8 } = await import('node:v8');
|
|
237
365
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
238
366
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
367
|
+
// Format heapStats
|
|
239
368
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
369
|
+
// Format heapSpaces
|
|
240
370
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
241
371
|
...space,
|
|
242
372
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -254,19 +384,23 @@ export class Frontend {
|
|
|
254
384
|
};
|
|
255
385
|
res.status(200).json(memoryReport);
|
|
256
386
|
});
|
|
387
|
+
// Endpoint to provide settings
|
|
257
388
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
258
389
|
this.log.debug('The frontend sent /api/settings');
|
|
259
390
|
res.json(await this.getApiSettings());
|
|
260
391
|
});
|
|
392
|
+
// Endpoint to provide plugins
|
|
261
393
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
262
394
|
this.log.debug('The frontend sent /api/plugins');
|
|
263
395
|
res.json(this.getBaseRegisteredPlugins());
|
|
264
396
|
});
|
|
397
|
+
// Endpoint to provide devices
|
|
265
398
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
266
399
|
this.log.debug('The frontend sent /api/devices');
|
|
267
400
|
const devices = await this.getDevices();
|
|
268
401
|
res.json(devices);
|
|
269
402
|
});
|
|
403
|
+
// Endpoint to view the matterbridge log
|
|
270
404
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
271
405
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
272
406
|
try {
|
|
@@ -279,6 +413,7 @@ export class Frontend {
|
|
|
279
413
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
280
414
|
}
|
|
281
415
|
});
|
|
416
|
+
// Endpoint to view the matter.js log
|
|
282
417
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
283
418
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
284
419
|
try {
|
|
@@ -291,6 +426,7 @@ export class Frontend {
|
|
|
291
426
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
292
427
|
}
|
|
293
428
|
});
|
|
429
|
+
// Endpoint to view the shelly log
|
|
294
430
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
295
431
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
296
432
|
try {
|
|
@@ -303,6 +439,7 @@ export class Frontend {
|
|
|
303
439
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
304
440
|
}
|
|
305
441
|
});
|
|
442
|
+
// Endpoint to download the matterbridge log
|
|
306
443
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
307
444
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
308
445
|
try {
|
|
@@ -315,6 +452,7 @@ export class Frontend {
|
|
|
315
452
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
316
453
|
}
|
|
317
454
|
res.type('text/plain');
|
|
455
|
+
// res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
318
456
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
319
457
|
if (error) {
|
|
320
458
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
@@ -322,6 +460,7 @@ export class Frontend {
|
|
|
322
460
|
}
|
|
323
461
|
});
|
|
324
462
|
});
|
|
463
|
+
// Endpoint to download the matter log
|
|
325
464
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
326
465
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
327
466
|
try {
|
|
@@ -341,6 +480,7 @@ export class Frontend {
|
|
|
341
480
|
}
|
|
342
481
|
});
|
|
343
482
|
});
|
|
483
|
+
// Endpoint to download the shelly log
|
|
344
484
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
345
485
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
346
486
|
try {
|
|
@@ -360,6 +500,7 @@ export class Frontend {
|
|
|
360
500
|
}
|
|
361
501
|
});
|
|
362
502
|
});
|
|
503
|
+
// Endpoint to download the matterbridge storage directory
|
|
363
504
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
364
505
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
365
506
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -370,6 +511,7 @@ export class Frontend {
|
|
|
370
511
|
}
|
|
371
512
|
});
|
|
372
513
|
});
|
|
514
|
+
// Endpoint to download the matter storage file
|
|
373
515
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
374
516
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
375
517
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -380,6 +522,7 @@ export class Frontend {
|
|
|
380
522
|
}
|
|
381
523
|
});
|
|
382
524
|
});
|
|
525
|
+
// Endpoint to download the matterbridge plugin directory
|
|
383
526
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
384
527
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
385
528
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -390,6 +533,7 @@ export class Frontend {
|
|
|
390
533
|
}
|
|
391
534
|
});
|
|
392
535
|
});
|
|
536
|
+
// Endpoint to download the matterbridge plugin config files
|
|
393
537
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
394
538
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
395
539
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
@@ -400,6 +544,7 @@ export class Frontend {
|
|
|
400
544
|
}
|
|
401
545
|
});
|
|
402
546
|
});
|
|
547
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
403
548
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
404
549
|
this.log.debug('The frontend sent /api/download-backup');
|
|
405
550
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -409,6 +554,7 @@ export class Frontend {
|
|
|
409
554
|
}
|
|
410
555
|
});
|
|
411
556
|
});
|
|
557
|
+
// Endpoint to upload a package
|
|
412
558
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
413
559
|
const { filename } = req.body;
|
|
414
560
|
const file = req.file;
|
|
@@ -418,10 +564,13 @@ export class Frontend {
|
|
|
418
564
|
return;
|
|
419
565
|
}
|
|
420
566
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
567
|
+
// Define the path where the plugin file will be saved
|
|
421
568
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
422
569
|
try {
|
|
570
|
+
// Move the uploaded file to the specified path
|
|
423
571
|
await fs.rename(file.path, filePath);
|
|
424
572
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
573
|
+
// Install the plugin package
|
|
425
574
|
if (filename.endsWith('.tgz')) {
|
|
426
575
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
427
576
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
@@ -440,6 +589,7 @@ export class Frontend {
|
|
|
440
589
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
441
590
|
}
|
|
442
591
|
});
|
|
592
|
+
// Fallback for routing (must be the last route)
|
|
443
593
|
this.expressApp.use((req, res) => {
|
|
444
594
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
445
595
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -448,12 +598,15 @@ export class Frontend {
|
|
|
448
598
|
}
|
|
449
599
|
async stop() {
|
|
450
600
|
this.log.debug('Stopping the frontend...');
|
|
601
|
+
// Remove listeners from the express app
|
|
451
602
|
if (this.expressApp) {
|
|
452
603
|
this.expressApp.removeAllListeners();
|
|
453
604
|
this.expressApp = undefined;
|
|
454
605
|
this.log.debug('Frontend app closed successfully');
|
|
455
606
|
}
|
|
607
|
+
// Close the WebSocket server
|
|
456
608
|
if (this.webSocketServer) {
|
|
609
|
+
// Close all active connections
|
|
457
610
|
this.webSocketServer.clients.forEach((client) => {
|
|
458
611
|
if (client.readyState === WebSocket.OPEN) {
|
|
459
612
|
client.close();
|
|
@@ -473,6 +626,7 @@ export class Frontend {
|
|
|
473
626
|
this.webSocketServer.removeAllListeners();
|
|
474
627
|
this.webSocketServer = undefined;
|
|
475
628
|
}
|
|
629
|
+
// Close the http server
|
|
476
630
|
if (this.httpServer) {
|
|
477
631
|
await withTimeout(new Promise((resolve) => {
|
|
478
632
|
this.httpServer?.close((error) => {
|
|
@@ -489,6 +643,7 @@ export class Frontend {
|
|
|
489
643
|
this.httpServer = undefined;
|
|
490
644
|
this.log.debug('Frontend http server closed successfully');
|
|
491
645
|
}
|
|
646
|
+
// Close the https server
|
|
492
647
|
if (this.httpsServer) {
|
|
493
648
|
await withTimeout(new Promise((resolve) => {
|
|
494
649
|
this.httpsServer?.close((error) => {
|
|
@@ -507,6 +662,7 @@ export class Frontend {
|
|
|
507
662
|
}
|
|
508
663
|
this.log.debug('Frontend stopped successfully');
|
|
509
664
|
}
|
|
665
|
+
// Function to format bytes to KB, MB, or GB
|
|
510
666
|
formatMemoryUsage = (bytes) => {
|
|
511
667
|
if (bytes >= 1024 ** 3) {
|
|
512
668
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -518,6 +674,7 @@ export class Frontend {
|
|
|
518
674
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
519
675
|
}
|
|
520
676
|
};
|
|
677
|
+
// Function to format system uptime with only the most significant unit
|
|
521
678
|
formatOsUpTime = (seconds) => {
|
|
522
679
|
if (seconds >= 86400) {
|
|
523
680
|
const days = Math.floor(seconds / 86400);
|
|
@@ -533,8 +690,14 @@ export class Frontend {
|
|
|
533
690
|
}
|
|
534
691
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
535
692
|
};
|
|
693
|
+
/**
|
|
694
|
+
* Retrieves the api settings data.
|
|
695
|
+
*
|
|
696
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
697
|
+
*/
|
|
536
698
|
async getApiSettings() {
|
|
537
699
|
const { lastCpuUsage } = await import('./cli.js');
|
|
700
|
+
// Update the system information
|
|
538
701
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
539
702
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
540
703
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -543,6 +706,7 @@ export class Frontend {
|
|
|
543
706
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
544
707
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
545
708
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
709
|
+
// Update the matterbridge information
|
|
546
710
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
547
711
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
548
712
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -561,6 +725,11 @@ export class Frontend {
|
|
|
561
725
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
562
726
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
563
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Retrieves the reachable attribute.
|
|
730
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
731
|
+
* @returns {boolean} The reachable attribute.
|
|
732
|
+
*/
|
|
564
733
|
getReachability(device) {
|
|
565
734
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
566
735
|
return false;
|
|
@@ -570,6 +739,11 @@ export class Frontend {
|
|
|
570
739
|
return true;
|
|
571
740
|
return false;
|
|
572
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Retrieves the power source attribute.
|
|
744
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
|
|
745
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
746
|
+
*/
|
|
573
747
|
getPowerSource(endpoint) {
|
|
574
748
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
575
749
|
return undefined;
|
|
@@ -585,13 +759,20 @@ export class Frontend {
|
|
|
585
759
|
}
|
|
586
760
|
return;
|
|
587
761
|
};
|
|
762
|
+
// Root endpoint
|
|
588
763
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
589
764
|
return powerSource(endpoint);
|
|
765
|
+
// Child endpoints
|
|
590
766
|
for (const child of endpoint.getChildEndpoints()) {
|
|
591
767
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
592
768
|
return powerSource(child);
|
|
593
769
|
}
|
|
594
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* Retrieves the cluster text description from a given device.
|
|
773
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
774
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
775
|
+
*/
|
|
595
776
|
getClusterTextFromDevice(device) {
|
|
596
777
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
597
778
|
return '';
|
|
@@ -632,7 +813,19 @@ export class Frontend {
|
|
|
632
813
|
};
|
|
633
814
|
let attributes = '';
|
|
634
815
|
let supportedModes = [];
|
|
816
|
+
/*
|
|
817
|
+
Object.keys(device.behaviors.supported).forEach((clusterName) => {
|
|
818
|
+
const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
|
|
819
|
+
// console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
|
|
820
|
+
if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
|
|
821
|
+
Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
|
|
822
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
*/
|
|
635
827
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
828
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
636
829
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
637
830
|
return;
|
|
638
831
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -724,8 +917,13 @@ export class Frontend {
|
|
|
724
917
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
725
918
|
attributes += `${getUserLabel(device)} `;
|
|
726
919
|
});
|
|
920
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
727
921
|
return attributes.trimStart().trimEnd();
|
|
728
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
925
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
926
|
+
*/
|
|
729
927
|
getBaseRegisteredPlugins() {
|
|
730
928
|
const baseRegisteredPlugins = [];
|
|
731
929
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -764,11 +962,18 @@ export class Frontend {
|
|
|
764
962
|
}
|
|
765
963
|
return baseRegisteredPlugins;
|
|
766
964
|
}
|
|
965
|
+
/**
|
|
966
|
+
* Retrieves the devices from Matterbridge.
|
|
967
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
968
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
|
|
969
|
+
*/
|
|
767
970
|
async getDevices(pluginName) {
|
|
768
971
|
const devices = [];
|
|
769
972
|
this.matterbridge.devices.forEach(async (device) => {
|
|
973
|
+
// Filter by pluginName if provided
|
|
770
974
|
if (pluginName && pluginName !== device.plugin)
|
|
771
975
|
return;
|
|
976
|
+
// Check if the device has the required properties
|
|
772
977
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
773
978
|
return;
|
|
774
979
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -788,22 +993,37 @@ export class Frontend {
|
|
|
788
993
|
});
|
|
789
994
|
return devices;
|
|
790
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
998
|
+
*
|
|
999
|
+
* Response for /api/clusters
|
|
1000
|
+
*
|
|
1001
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1002
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1003
|
+
* @returns {Promise<ApiClustersResponse | undefined>} A promise that resolves to the clusters or undefined if not found.
|
|
1004
|
+
*/
|
|
791
1005
|
getClusters(pluginName, endpointNumber) {
|
|
792
1006
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
793
1007
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
794
1008
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
795
1009
|
return;
|
|
796
1010
|
}
|
|
1011
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1012
|
+
// Get the device types from the main endpoint
|
|
797
1013
|
const deviceTypes = [];
|
|
798
1014
|
const clusters = [];
|
|
799
1015
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
800
1016
|
deviceTypes.push(d.deviceType);
|
|
801
1017
|
});
|
|
1018
|
+
// Get the clusters from the main endpoint
|
|
802
1019
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
803
1020
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
804
1021
|
return;
|
|
805
1022
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
806
1023
|
return;
|
|
1024
|
+
// console.log(
|
|
1025
|
+
// `${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}`,
|
|
1026
|
+
// );
|
|
807
1027
|
clusters.push({
|
|
808
1028
|
endpoint: endpoint.number.toString(),
|
|
809
1029
|
id: 'main',
|
|
@@ -816,12 +1036,18 @@ export class Frontend {
|
|
|
816
1036
|
attributeLocalValue: attributeValue,
|
|
817
1037
|
});
|
|
818
1038
|
});
|
|
1039
|
+
// Get the child endpoints
|
|
819
1040
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1041
|
+
// if (childEndpoints.length === 0) {
|
|
1042
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1043
|
+
// }
|
|
820
1044
|
childEndpoints.forEach((childEndpoint) => {
|
|
821
1045
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
822
1046
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
823
1047
|
return;
|
|
824
1048
|
}
|
|
1049
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1050
|
+
// Get the device types of the child endpoint
|
|
825
1051
|
const deviceTypes = [];
|
|
826
1052
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
827
1053
|
deviceTypes.push(d.deviceType);
|
|
@@ -831,9 +1057,12 @@ export class Frontend {
|
|
|
831
1057
|
return;
|
|
832
1058
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
833
1059
|
return;
|
|
1060
|
+
// console.log(
|
|
1061
|
+
// `${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}`,
|
|
1062
|
+
// );
|
|
834
1063
|
clusters.push({
|
|
835
1064
|
endpoint: childEndpoint.number.toString(),
|
|
836
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1065
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
837
1066
|
deviceTypes,
|
|
838
1067
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
839
1068
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -846,6 +1075,13 @@ export class Frontend {
|
|
|
846
1075
|
});
|
|
847
1076
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
848
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1080
|
+
*
|
|
1081
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1082
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1083
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1084
|
+
*/
|
|
849
1085
|
async wsMessageHandler(client, message) {
|
|
850
1086
|
let data;
|
|
851
1087
|
try {
|
|
@@ -892,8 +1128,10 @@ export class Frontend {
|
|
|
892
1128
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
893
1129
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
894
1130
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1131
|
+
// The install comes from InstallPlugins
|
|
895
1132
|
this.matterbridge.plugins.add(packageName).then((plugin) => {
|
|
896
1133
|
if (plugin) {
|
|
1134
|
+
// The plugin is not registered
|
|
897
1135
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
898
1136
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
|
|
899
1137
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
@@ -901,6 +1139,7 @@ export class Frontend {
|
|
|
901
1139
|
});
|
|
902
1140
|
}
|
|
903
1141
|
else {
|
|
1142
|
+
// The plugin is already registered
|
|
904
1143
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
905
1144
|
this.wssSendRefreshRequired('plugins');
|
|
906
1145
|
this.wssSendRestartRequired();
|
|
@@ -908,6 +1147,7 @@ export class Frontend {
|
|
|
908
1147
|
});
|
|
909
1148
|
}
|
|
910
1149
|
else {
|
|
1150
|
+
// The package is matterbridge
|
|
911
1151
|
if (this.matterbridge.restartMode !== '') {
|
|
912
1152
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
913
1153
|
this.matterbridge.shutdownProcess();
|
|
@@ -929,6 +1169,7 @@ export class Frontend {
|
|
|
929
1169
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
930
1170
|
return;
|
|
931
1171
|
}
|
|
1172
|
+
// The package is a plugin
|
|
932
1173
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
933
1174
|
if (plugin) {
|
|
934
1175
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -937,6 +1178,7 @@ export class Frontend {
|
|
|
937
1178
|
this.wssSendRefreshRequired('plugins');
|
|
938
1179
|
this.wssSendRefreshRequired('devices');
|
|
939
1180
|
}
|
|
1181
|
+
// Uninstall the package
|
|
940
1182
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
941
1183
|
this.matterbridge
|
|
942
1184
|
.spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1248,22 +1490,22 @@ export class Frontend {
|
|
|
1248
1490
|
if (isValidString(data.params.value, 4)) {
|
|
1249
1491
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1250
1492
|
if (data.params.value === 'Debug') {
|
|
1251
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1493
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1252
1494
|
}
|
|
1253
1495
|
else if (data.params.value === 'Info') {
|
|
1254
|
-
await this.matterbridge.setLogLevel("info");
|
|
1496
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1255
1497
|
}
|
|
1256
1498
|
else if (data.params.value === 'Notice') {
|
|
1257
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1499
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1258
1500
|
}
|
|
1259
1501
|
else if (data.params.value === 'Warn') {
|
|
1260
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1502
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1261
1503
|
}
|
|
1262
1504
|
else if (data.params.value === 'Error') {
|
|
1263
|
-
await this.matterbridge.setLogLevel("error");
|
|
1505
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1264
1506
|
}
|
|
1265
1507
|
else if (data.params.value === 'Fatal') {
|
|
1266
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1508
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1267
1509
|
}
|
|
1268
1510
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1269
1511
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1274,6 +1516,7 @@ export class Frontend {
|
|
|
1274
1516
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1275
1517
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1276
1518
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1519
|
+
// Create the file logger for matterbridge
|
|
1277
1520
|
if (data.params.value)
|
|
1278
1521
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1279
1522
|
else
|
|
@@ -1438,15 +1681,19 @@ export class Frontend {
|
|
|
1438
1681
|
return;
|
|
1439
1682
|
}
|
|
1440
1683
|
const config = plugin.configJson;
|
|
1684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1441
1685
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1686
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1442
1687
|
if (select === 'serial')
|
|
1443
1688
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1444
1689
|
if (select === 'name')
|
|
1445
1690
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1446
1691
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1692
|
+
// Remove postfix from the serial if it exists
|
|
1447
1693
|
if (config.postfix) {
|
|
1448
1694
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1449
1695
|
}
|
|
1696
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1450
1697
|
if (isValidArray(config.whiteList, 1)) {
|
|
1451
1698
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1452
1699
|
config.whiteList.push(data.params.serial);
|
|
@@ -1455,6 +1702,7 @@ export class Frontend {
|
|
|
1455
1702
|
config.whiteList.push(data.params.name);
|
|
1456
1703
|
}
|
|
1457
1704
|
}
|
|
1705
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1458
1706
|
if (isValidArray(config.blackList, 1)) {
|
|
1459
1707
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1460
1708
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1484,7 +1732,9 @@ export class Frontend {
|
|
|
1484
1732
|
return;
|
|
1485
1733
|
}
|
|
1486
1734
|
const config = plugin.configJson;
|
|
1735
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1487
1736
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1737
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1488
1738
|
if (select === 'serial')
|
|
1489
1739
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1490
1740
|
if (select === 'name')
|
|
@@ -1493,6 +1743,7 @@ export class Frontend {
|
|
|
1493
1743
|
if (config.postfix) {
|
|
1494
1744
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1495
1745
|
}
|
|
1746
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1496
1747
|
if (isValidArray(config.whiteList, 1)) {
|
|
1497
1748
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1498
1749
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1501,6 +1752,7 @@ export class Frontend {
|
|
|
1501
1752
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1502
1753
|
}
|
|
1503
1754
|
}
|
|
1755
|
+
// Add the serial to the blackList
|
|
1504
1756
|
if (isValidArray(config.blackList)) {
|
|
1505
1757
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1506
1758
|
config.blackList.push(data.params.serial);
|
|
@@ -1533,114 +1785,219 @@ export class Frontend {
|
|
|
1533
1785
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1534
1786
|
}
|
|
1535
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1790
|
+
*
|
|
1791
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1792
|
+
* @param {string} time - The time string of the message
|
|
1793
|
+
* @param {string} name - The logger name of the message
|
|
1794
|
+
* @param {string} message - The content of the message.
|
|
1795
|
+
*
|
|
1796
|
+
* @remark
|
|
1797
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1798
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1799
|
+
* The function sends the message to all connected clients.
|
|
1800
|
+
*/
|
|
1536
1801
|
wssSendMessage(level, time, name, message) {
|
|
1537
1802
|
if (!level || !time || !name || !message)
|
|
1538
1803
|
return;
|
|
1804
|
+
// Remove ANSI escape codes from the message
|
|
1805
|
+
// eslint-disable-next-line no-control-regex
|
|
1539
1806
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1807
|
+
// Remove leading asterisks from the message
|
|
1540
1808
|
message = message.replace(/^\*+/, '');
|
|
1809
|
+
// Replace all occurrences of \t and \n
|
|
1541
1810
|
message = message.replace(/[\t\n]/g, '');
|
|
1811
|
+
// Remove non-printable characters
|
|
1812
|
+
// eslint-disable-next-line no-control-regex
|
|
1542
1813
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1814
|
+
// Replace all occurrences of \" with "
|
|
1543
1815
|
message = message.replace(/\\"/g, '"');
|
|
1816
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1544
1817
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1818
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1545
1819
|
const maxContinuousLength = 100;
|
|
1546
1820
|
const keepStartLength = 20;
|
|
1547
1821
|
const keepEndLength = 20;
|
|
1822
|
+
// Split the message into words
|
|
1548
1823
|
message = message
|
|
1549
1824
|
.split(' ')
|
|
1550
1825
|
.map((word) => {
|
|
1826
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1551
1827
|
if (word.length > maxContinuousLength) {
|
|
1552
1828
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1553
1829
|
}
|
|
1554
1830
|
return word;
|
|
1555
1831
|
})
|
|
1556
1832
|
.join(' ');
|
|
1833
|
+
// Send the message to all connected clients
|
|
1557
1834
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1558
1835
|
if (client.readyState === WebSocket.OPEN) {
|
|
1559
1836
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1560
1837
|
}
|
|
1561
1838
|
});
|
|
1562
1839
|
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1842
|
+
*
|
|
1843
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1844
|
+
* possible values:
|
|
1845
|
+
* - 'matterbridgeLatestVersion'
|
|
1846
|
+
* - 'matterbridgeAdvertise'
|
|
1847
|
+
* - 'online'
|
|
1848
|
+
* - 'offline'
|
|
1849
|
+
* - 'reachability'
|
|
1850
|
+
* - 'settings'
|
|
1851
|
+
* - 'plugins'
|
|
1852
|
+
* - 'pluginsRestart'
|
|
1853
|
+
* - 'devices'
|
|
1854
|
+
* - 'fabrics'
|
|
1855
|
+
* - 'sessions'
|
|
1856
|
+
*/
|
|
1563
1857
|
wssSendRefreshRequired(changed = null) {
|
|
1564
1858
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1859
|
+
// Send the message to all connected clients
|
|
1565
1860
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1566
1861
|
if (client.readyState === WebSocket.OPEN) {
|
|
1567
1862
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1568
1863
|
}
|
|
1569
1864
|
});
|
|
1570
1865
|
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1868
|
+
*
|
|
1869
|
+
*/
|
|
1571
1870
|
wssSendRestartRequired(snackbar = true) {
|
|
1572
1871
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1573
1872
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1574
1873
|
if (snackbar === true)
|
|
1575
1874
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1875
|
+
// Send the message to all connected clients
|
|
1576
1876
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1577
1877
|
if (client.readyState === WebSocket.OPEN) {
|
|
1578
1878
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1579
1879
|
}
|
|
1580
1880
|
});
|
|
1581
1881
|
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1884
|
+
*
|
|
1885
|
+
*/
|
|
1582
1886
|
wssSendUpdateRequired() {
|
|
1583
1887
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1584
1888
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1889
|
+
// Send the message to all connected clients
|
|
1585
1890
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1586
1891
|
if (client.readyState === WebSocket.OPEN) {
|
|
1587
1892
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1588
1893
|
}
|
|
1589
1894
|
});
|
|
1590
1895
|
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Sends a cpu update message to all connected clients.
|
|
1898
|
+
*
|
|
1899
|
+
*/
|
|
1591
1900
|
wssSendCpuUpdate(cpuUsage) {
|
|
1592
1901
|
if (hasParameter('debug'))
|
|
1593
1902
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1903
|
+
// Send the message to all connected clients
|
|
1594
1904
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1595
1905
|
if (client.readyState === WebSocket.OPEN) {
|
|
1596
1906
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1597
1907
|
}
|
|
1598
1908
|
});
|
|
1599
1909
|
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Sends a memory update message to all connected clients.
|
|
1912
|
+
*
|
|
1913
|
+
*/
|
|
1600
1914
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1601
1915
|
if (hasParameter('debug'))
|
|
1602
1916
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1917
|
+
// Send the message to all connected clients
|
|
1603
1918
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1604
1919
|
if (client.readyState === WebSocket.OPEN) {
|
|
1605
1920
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1606
1921
|
}
|
|
1607
1922
|
});
|
|
1608
1923
|
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Sends an uptime update message to all connected clients.
|
|
1926
|
+
*
|
|
1927
|
+
*/
|
|
1609
1928
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1610
1929
|
if (hasParameter('debug'))
|
|
1611
1930
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1931
|
+
// Send the message to all connected clients
|
|
1612
1932
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1613
1933
|
if (client.readyState === WebSocket.OPEN) {
|
|
1614
1934
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1615
1935
|
}
|
|
1616
1936
|
});
|
|
1617
1937
|
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Sends an open snackbar message to all connected clients.
|
|
1940
|
+
* @param {string} message - The message to send.
|
|
1941
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
1942
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
1943
|
+
*
|
|
1944
|
+
*/
|
|
1618
1945
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1619
1946
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1947
|
+
// Send the message to all connected clients
|
|
1620
1948
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1621
1949
|
if (client.readyState === WebSocket.OPEN) {
|
|
1622
1950
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1623
1951
|
}
|
|
1624
1952
|
});
|
|
1625
1953
|
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Sends a close snackbar message to all connected clients.
|
|
1956
|
+
* @param {string} message - The message to send.
|
|
1957
|
+
*
|
|
1958
|
+
*/
|
|
1626
1959
|
wssSendCloseSnackbarMessage(message) {
|
|
1627
1960
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
1961
|
+
// Send the message to all connected clients
|
|
1628
1962
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1629
1963
|
if (client.readyState === WebSocket.OPEN) {
|
|
1630
1964
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1631
1965
|
}
|
|
1632
1966
|
});
|
|
1633
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
1970
|
+
*
|
|
1971
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
1972
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
1973
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
1974
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
1975
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
1976
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
1977
|
+
*
|
|
1978
|
+
* @remarks
|
|
1979
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
1980
|
+
* with the updated attribute information.
|
|
1981
|
+
*/
|
|
1634
1982
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1635
1983
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
1984
|
+
// Send the message to all connected clients
|
|
1636
1985
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1637
1986
|
if (client.readyState === WebSocket.OPEN) {
|
|
1638
1987
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1639
1988
|
}
|
|
1640
1989
|
});
|
|
1641
1990
|
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Sends a message to all connected clients.
|
|
1993
|
+
* @param {number} id - The message id.
|
|
1994
|
+
* @param {string} method - The message method.
|
|
1995
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
1996
|
+
*
|
|
1997
|
+
*/
|
|
1642
1998
|
wssBroadcastMessage(id, method, params) {
|
|
1643
1999
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2000
|
+
// Send the message to all connected clients
|
|
1644
2001
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1645
2002
|
if (client.readyState === WebSocket.OPEN) {
|
|
1646
2003
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1648,3 +2005,4 @@ export class Frontend {
|
|
|
1648
2005
|
});
|
|
1649
2006
|
}
|
|
1650
2007
|
}
|
|
2008
|
+
//# sourceMappingURL=frontend.js.map
|