matterbridge 3.0.4-dev-20250525-b1cbfb7 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +37 -2
- 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/frontend.d.ts +256 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +363 -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 +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -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 +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 +445 -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 +1398 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +61 -4
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +629 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +563 -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 +1053 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +903 -23
- 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 +82 -0
- package/dist/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/roboticVacuumCleaner.js +78 -3
- 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 +75 -0
- package/dist/waterHeater.d.ts.map +1 -0
- package/dist/waterHeater.js +52 -0
- 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);
|
|
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 '';
|
|
@@ -633,6 +814,7 @@ export class Frontend {
|
|
|
633
814
|
let attributes = '';
|
|
634
815
|
let supportedModes = [];
|
|
635
816
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
817
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
636
818
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
637
819
|
return;
|
|
638
820
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -724,8 +906,13 @@ export class Frontend {
|
|
|
724
906
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
725
907
|
attributes += `${getUserLabel(device)} `;
|
|
726
908
|
});
|
|
909
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
727
910
|
return attributes.trimStart().trimEnd();
|
|
728
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
914
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
915
|
+
*/
|
|
729
916
|
getBaseRegisteredPlugins() {
|
|
730
917
|
const baseRegisteredPlugins = [];
|
|
731
918
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -764,11 +951,18 @@ export class Frontend {
|
|
|
764
951
|
}
|
|
765
952
|
return baseRegisteredPlugins;
|
|
766
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Retrieves the devices from Matterbridge.
|
|
956
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
957
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
|
|
958
|
+
*/
|
|
767
959
|
async getDevices(pluginName) {
|
|
768
960
|
const devices = [];
|
|
769
961
|
this.matterbridge.devices.forEach(async (device) => {
|
|
962
|
+
// Filter by pluginName if provided
|
|
770
963
|
if (pluginName && pluginName !== device.plugin)
|
|
771
964
|
return;
|
|
965
|
+
// Check if the device has the required properties
|
|
772
966
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
773
967
|
return;
|
|
774
968
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -788,22 +982,37 @@ export class Frontend {
|
|
|
788
982
|
});
|
|
789
983
|
return devices;
|
|
790
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
987
|
+
*
|
|
988
|
+
* Response for /api/clusters
|
|
989
|
+
*
|
|
990
|
+
* @param {string} pluginName - The name of the plugin.
|
|
991
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
992
|
+
* @returns {Promise<ApiClustersResponse | undefined>} A promise that resolves to the clusters or undefined if not found.
|
|
993
|
+
*/
|
|
791
994
|
getClusters(pluginName, endpointNumber) {
|
|
792
995
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
793
996
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
794
997
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
795
998
|
return;
|
|
796
999
|
}
|
|
1000
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1001
|
+
// Get the device types from the main endpoint
|
|
797
1002
|
const deviceTypes = [];
|
|
798
1003
|
const clusters = [];
|
|
799
1004
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
800
1005
|
deviceTypes.push(d.deviceType);
|
|
801
1006
|
});
|
|
1007
|
+
// Get the clusters from the main endpoint
|
|
802
1008
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
803
1009
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
804
1010
|
return;
|
|
805
1011
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
806
1012
|
return;
|
|
1013
|
+
// console.log(
|
|
1014
|
+
// `${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}`,
|
|
1015
|
+
// );
|
|
807
1016
|
clusters.push({
|
|
808
1017
|
endpoint: endpoint.number.toString(),
|
|
809
1018
|
id: 'main',
|
|
@@ -816,12 +1025,18 @@ export class Frontend {
|
|
|
816
1025
|
attributeLocalValue: attributeValue,
|
|
817
1026
|
});
|
|
818
1027
|
});
|
|
1028
|
+
// Get the child endpoints
|
|
819
1029
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1030
|
+
// if (childEndpoints.length === 0) {
|
|
1031
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1032
|
+
// }
|
|
820
1033
|
childEndpoints.forEach((childEndpoint) => {
|
|
821
1034
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
822
1035
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
823
1036
|
return;
|
|
824
1037
|
}
|
|
1038
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1039
|
+
// Get the device types of the child endpoint
|
|
825
1040
|
const deviceTypes = [];
|
|
826
1041
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
827
1042
|
deviceTypes.push(d.deviceType);
|
|
@@ -831,9 +1046,12 @@ export class Frontend {
|
|
|
831
1046
|
return;
|
|
832
1047
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
833
1048
|
return;
|
|
1049
|
+
// console.log(
|
|
1050
|
+
// `${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}`,
|
|
1051
|
+
// );
|
|
834
1052
|
clusters.push({
|
|
835
1053
|
endpoint: childEndpoint.number.toString(),
|
|
836
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1054
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
837
1055
|
deviceTypes,
|
|
838
1056
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
839
1057
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -846,6 +1064,13 @@ export class Frontend {
|
|
|
846
1064
|
});
|
|
847
1065
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
848
1066
|
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1069
|
+
*
|
|
1070
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1071
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1072
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1073
|
+
*/
|
|
849
1074
|
async wsMessageHandler(client, message) {
|
|
850
1075
|
let data;
|
|
851
1076
|
try {
|
|
@@ -892,8 +1117,10 @@ export class Frontend {
|
|
|
892
1117
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
893
1118
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
894
1119
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1120
|
+
// The install comes from InstallPlugins
|
|
895
1121
|
this.matterbridge.plugins.add(packageName).then((plugin) => {
|
|
896
1122
|
if (plugin) {
|
|
1123
|
+
// The plugin is not registered
|
|
897
1124
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
898
1125
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
|
|
899
1126
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
@@ -901,6 +1128,7 @@ export class Frontend {
|
|
|
901
1128
|
});
|
|
902
1129
|
}
|
|
903
1130
|
else {
|
|
1131
|
+
// The plugin is already registered
|
|
904
1132
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
905
1133
|
this.wssSendRefreshRequired('plugins');
|
|
906
1134
|
this.wssSendRestartRequired();
|
|
@@ -908,6 +1136,7 @@ export class Frontend {
|
|
|
908
1136
|
});
|
|
909
1137
|
}
|
|
910
1138
|
else {
|
|
1139
|
+
// The package is matterbridge
|
|
911
1140
|
if (this.matterbridge.restartMode !== '') {
|
|
912
1141
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
913
1142
|
this.matterbridge.shutdownProcess();
|
|
@@ -929,6 +1158,7 @@ export class Frontend {
|
|
|
929
1158
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
930
1159
|
return;
|
|
931
1160
|
}
|
|
1161
|
+
// The package is a plugin
|
|
932
1162
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
933
1163
|
if (plugin) {
|
|
934
1164
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -937,6 +1167,7 @@ export class Frontend {
|
|
|
937
1167
|
this.wssSendRefreshRequired('plugins');
|
|
938
1168
|
this.wssSendRefreshRequired('devices');
|
|
939
1169
|
}
|
|
1170
|
+
// Uninstall the package
|
|
940
1171
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
941
1172
|
this.matterbridge
|
|
942
1173
|
.spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1248,22 +1479,22 @@ export class Frontend {
|
|
|
1248
1479
|
if (isValidString(data.params.value, 4)) {
|
|
1249
1480
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1250
1481
|
if (data.params.value === 'Debug') {
|
|
1251
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1482
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1252
1483
|
}
|
|
1253
1484
|
else if (data.params.value === 'Info') {
|
|
1254
|
-
await this.matterbridge.setLogLevel("info");
|
|
1485
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1255
1486
|
}
|
|
1256
1487
|
else if (data.params.value === 'Notice') {
|
|
1257
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1488
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1258
1489
|
}
|
|
1259
1490
|
else if (data.params.value === 'Warn') {
|
|
1260
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1491
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1261
1492
|
}
|
|
1262
1493
|
else if (data.params.value === 'Error') {
|
|
1263
|
-
await this.matterbridge.setLogLevel("error");
|
|
1494
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1264
1495
|
}
|
|
1265
1496
|
else if (data.params.value === 'Fatal') {
|
|
1266
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1497
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1267
1498
|
}
|
|
1268
1499
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1269
1500
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1274,6 +1505,7 @@ export class Frontend {
|
|
|
1274
1505
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1275
1506
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1276
1507
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1508
|
+
// Create the file logger for matterbridge
|
|
1277
1509
|
if (data.params.value)
|
|
1278
1510
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1279
1511
|
else
|
|
@@ -1438,15 +1670,19 @@ export class Frontend {
|
|
|
1438
1670
|
return;
|
|
1439
1671
|
}
|
|
1440
1672
|
const config = plugin.configJson;
|
|
1673
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1441
1674
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1675
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1442
1676
|
if (select === 'serial')
|
|
1443
1677
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1444
1678
|
if (select === 'name')
|
|
1445
1679
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1446
1680
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1681
|
+
// Remove postfix from the serial if it exists
|
|
1447
1682
|
if (config.postfix) {
|
|
1448
1683
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1449
1684
|
}
|
|
1685
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1450
1686
|
if (isValidArray(config.whiteList, 1)) {
|
|
1451
1687
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1452
1688
|
config.whiteList.push(data.params.serial);
|
|
@@ -1455,6 +1691,7 @@ export class Frontend {
|
|
|
1455
1691
|
config.whiteList.push(data.params.name);
|
|
1456
1692
|
}
|
|
1457
1693
|
}
|
|
1694
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1458
1695
|
if (isValidArray(config.blackList, 1)) {
|
|
1459
1696
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1460
1697
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1484,7 +1721,9 @@ export class Frontend {
|
|
|
1484
1721
|
return;
|
|
1485
1722
|
}
|
|
1486
1723
|
const config = plugin.configJson;
|
|
1724
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1487
1725
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1726
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1488
1727
|
if (select === 'serial')
|
|
1489
1728
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1490
1729
|
if (select === 'name')
|
|
@@ -1493,6 +1732,7 @@ export class Frontend {
|
|
|
1493
1732
|
if (config.postfix) {
|
|
1494
1733
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1495
1734
|
}
|
|
1735
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1496
1736
|
if (isValidArray(config.whiteList, 1)) {
|
|
1497
1737
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1498
1738
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1501,6 +1741,7 @@ export class Frontend {
|
|
|
1501
1741
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1502
1742
|
}
|
|
1503
1743
|
}
|
|
1744
|
+
// Add the serial to the blackList
|
|
1504
1745
|
if (isValidArray(config.blackList)) {
|
|
1505
1746
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1506
1747
|
config.blackList.push(data.params.serial);
|
|
@@ -1533,114 +1774,219 @@ export class Frontend {
|
|
|
1533
1774
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1534
1775
|
}
|
|
1535
1776
|
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1779
|
+
*
|
|
1780
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1781
|
+
* @param {string} time - The time string of the message
|
|
1782
|
+
* @param {string} name - The logger name of the message
|
|
1783
|
+
* @param {string} message - The content of the message.
|
|
1784
|
+
*
|
|
1785
|
+
* @remark
|
|
1786
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1787
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1788
|
+
* The function sends the message to all connected clients.
|
|
1789
|
+
*/
|
|
1536
1790
|
wssSendMessage(level, time, name, message) {
|
|
1537
1791
|
if (!level || !time || !name || !message)
|
|
1538
1792
|
return;
|
|
1793
|
+
// Remove ANSI escape codes from the message
|
|
1794
|
+
// eslint-disable-next-line no-control-regex
|
|
1539
1795
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1796
|
+
// Remove leading asterisks from the message
|
|
1540
1797
|
message = message.replace(/^\*+/, '');
|
|
1798
|
+
// Replace all occurrences of \t and \n
|
|
1541
1799
|
message = message.replace(/[\t\n]/g, '');
|
|
1800
|
+
// Remove non-printable characters
|
|
1801
|
+
// eslint-disable-next-line no-control-regex
|
|
1542
1802
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1803
|
+
// Replace all occurrences of \" with "
|
|
1543
1804
|
message = message.replace(/\\"/g, '"');
|
|
1805
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1544
1806
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1807
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1545
1808
|
const maxContinuousLength = 100;
|
|
1546
1809
|
const keepStartLength = 20;
|
|
1547
1810
|
const keepEndLength = 20;
|
|
1811
|
+
// Split the message into words
|
|
1548
1812
|
message = message
|
|
1549
1813
|
.split(' ')
|
|
1550
1814
|
.map((word) => {
|
|
1815
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1551
1816
|
if (word.length > maxContinuousLength) {
|
|
1552
1817
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1553
1818
|
}
|
|
1554
1819
|
return word;
|
|
1555
1820
|
})
|
|
1556
1821
|
.join(' ');
|
|
1822
|
+
// Send the message to all connected clients
|
|
1557
1823
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1558
1824
|
if (client.readyState === WebSocket.OPEN) {
|
|
1559
1825
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1560
1826
|
}
|
|
1561
1827
|
});
|
|
1562
1828
|
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1831
|
+
*
|
|
1832
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1833
|
+
* possible values:
|
|
1834
|
+
* - 'matterbridgeLatestVersion'
|
|
1835
|
+
* - 'matterbridgeAdvertise'
|
|
1836
|
+
* - 'online'
|
|
1837
|
+
* - 'offline'
|
|
1838
|
+
* - 'reachability'
|
|
1839
|
+
* - 'settings'
|
|
1840
|
+
* - 'plugins'
|
|
1841
|
+
* - 'pluginsRestart'
|
|
1842
|
+
* - 'devices'
|
|
1843
|
+
* - 'fabrics'
|
|
1844
|
+
* - 'sessions'
|
|
1845
|
+
*/
|
|
1563
1846
|
wssSendRefreshRequired(changed = null) {
|
|
1564
1847
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1848
|
+
// Send the message to all connected clients
|
|
1565
1849
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1566
1850
|
if (client.readyState === WebSocket.OPEN) {
|
|
1567
1851
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1568
1852
|
}
|
|
1569
1853
|
});
|
|
1570
1854
|
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1857
|
+
*
|
|
1858
|
+
*/
|
|
1571
1859
|
wssSendRestartRequired(snackbar = true) {
|
|
1572
1860
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1573
1861
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1574
1862
|
if (snackbar === true)
|
|
1575
1863
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1864
|
+
// Send the message to all connected clients
|
|
1576
1865
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1577
1866
|
if (client.readyState === WebSocket.OPEN) {
|
|
1578
1867
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1579
1868
|
}
|
|
1580
1869
|
});
|
|
1581
1870
|
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1873
|
+
*
|
|
1874
|
+
*/
|
|
1582
1875
|
wssSendUpdateRequired() {
|
|
1583
1876
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1584
1877
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1878
|
+
// Send the message to all connected clients
|
|
1585
1879
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1586
1880
|
if (client.readyState === WebSocket.OPEN) {
|
|
1587
1881
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1588
1882
|
}
|
|
1589
1883
|
});
|
|
1590
1884
|
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Sends a cpu update message to all connected clients.
|
|
1887
|
+
*
|
|
1888
|
+
*/
|
|
1591
1889
|
wssSendCpuUpdate(cpuUsage) {
|
|
1592
1890
|
if (hasParameter('debug'))
|
|
1593
1891
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1892
|
+
// Send the message to all connected clients
|
|
1594
1893
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1595
1894
|
if (client.readyState === WebSocket.OPEN) {
|
|
1596
1895
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1597
1896
|
}
|
|
1598
1897
|
});
|
|
1599
1898
|
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Sends a memory update message to all connected clients.
|
|
1901
|
+
*
|
|
1902
|
+
*/
|
|
1600
1903
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1601
1904
|
if (hasParameter('debug'))
|
|
1602
1905
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1906
|
+
// Send the message to all connected clients
|
|
1603
1907
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1604
1908
|
if (client.readyState === WebSocket.OPEN) {
|
|
1605
1909
|
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
1910
|
}
|
|
1607
1911
|
});
|
|
1608
1912
|
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Sends an uptime update message to all connected clients.
|
|
1915
|
+
*
|
|
1916
|
+
*/
|
|
1609
1917
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1610
1918
|
if (hasParameter('debug'))
|
|
1611
1919
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1920
|
+
// Send the message to all connected clients
|
|
1612
1921
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1613
1922
|
if (client.readyState === WebSocket.OPEN) {
|
|
1614
1923
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1615
1924
|
}
|
|
1616
1925
|
});
|
|
1617
1926
|
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Sends an open snackbar message to all connected clients.
|
|
1929
|
+
* @param {string} message - The message to send.
|
|
1930
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
1931
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
1932
|
+
*
|
|
1933
|
+
*/
|
|
1618
1934
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1619
1935
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1936
|
+
// Send the message to all connected clients
|
|
1620
1937
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1621
1938
|
if (client.readyState === WebSocket.OPEN) {
|
|
1622
1939
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1623
1940
|
}
|
|
1624
1941
|
});
|
|
1625
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Sends a close snackbar message to all connected clients.
|
|
1945
|
+
* @param {string} message - The message to send.
|
|
1946
|
+
*
|
|
1947
|
+
*/
|
|
1626
1948
|
wssSendCloseSnackbarMessage(message) {
|
|
1627
1949
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
1950
|
+
// Send the message to all connected clients
|
|
1628
1951
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1629
1952
|
if (client.readyState === WebSocket.OPEN) {
|
|
1630
1953
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1631
1954
|
}
|
|
1632
1955
|
});
|
|
1633
1956
|
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
1959
|
+
*
|
|
1960
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
1961
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
1962
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
1963
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
1964
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
1965
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
1966
|
+
*
|
|
1967
|
+
* @remarks
|
|
1968
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
1969
|
+
* with the updated attribute information.
|
|
1970
|
+
*/
|
|
1634
1971
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1635
1972
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
1973
|
+
// Send the message to all connected clients
|
|
1636
1974
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1637
1975
|
if (client.readyState === WebSocket.OPEN) {
|
|
1638
1976
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1639
1977
|
}
|
|
1640
1978
|
});
|
|
1641
1979
|
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Sends a message to all connected clients.
|
|
1982
|
+
* @param {number} id - The message id.
|
|
1983
|
+
* @param {string} method - The message method.
|
|
1984
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
1985
|
+
*
|
|
1986
|
+
*/
|
|
1642
1987
|
wssBroadcastMessage(id, method, params) {
|
|
1643
1988
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
1989
|
+
// Send the message to all connected clients
|
|
1644
1990
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1645
1991
|
if (client.readyState === WebSocket.OPEN) {
|
|
1646
1992
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1648,3 +1994,4 @@ export class Frontend {
|
|
|
1648
1994
|
});
|
|
1649
1995
|
}
|
|
1650
1996
|
}
|
|
1997
|
+
//# sourceMappingURL=frontend.js.map
|