matterbridge 3.0.1-dev-20250506-c77ed36 → 3.0.1
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 +4 -4
- 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 +240 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +328 -15
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -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 +433 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +747 -46
- 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 +1166 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +48 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +494 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +431 -12
- 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 +956 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +801 -11
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2706 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +142 -9
- 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 +187 -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/shelly.d.ts +92 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +146 -6
- 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 +32 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +52 -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/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 +40 -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 +65 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +10 -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/isvalid.d.ts +95 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +93 -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/parameter.d.ts +58 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +53 -0
- package/dist/utils/parameter.js.map +1 -0
- package/dist/utils/wait.d.ts +43 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +48 -5
- package/dist/utils/wait.js.map +1 -0
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.f221e2c1.js → main.15906009.js} +3 -3
- package/frontend/build/static/js/{main.f221e2c1.js.map → main.15906009.js.map} +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- /package/frontend/build/static/js/{main.f221e2c1.js.LICENSE.txt → main.15906009.js.LICENSE.txt} +0 -0
package/dist/frontend.js
CHANGED
|
@@ -1,28 +1,111 @@
|
|
|
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 { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
25
|
+
// Node modules
|
|
2
26
|
import { createServer } from 'node:http';
|
|
3
27
|
import https from 'node:https';
|
|
4
28
|
import os from 'node:os';
|
|
5
29
|
import path from 'node:path';
|
|
6
30
|
import { promises as fs } from 'node:fs';
|
|
31
|
+
// Third-party modules
|
|
7
32
|
import express from 'express';
|
|
8
33
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
34
|
import multer from 'multer';
|
|
35
|
+
// AnsiLogger module
|
|
10
36
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
|
|
37
|
+
// Matterbridge
|
|
11
38
|
import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/export.js';
|
|
12
39
|
import { plg } from './matterbridgeTypes.js';
|
|
13
40
|
import { hasParameter } from './utils/export.js';
|
|
14
41
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
42
|
+
/**
|
|
43
|
+
* Websocket message ID for logging.
|
|
44
|
+
* @constant {number}
|
|
45
|
+
*/
|
|
15
46
|
export const WS_ID_LOG = 0;
|
|
47
|
+
/**
|
|
48
|
+
* Websocket message ID indicating a refresh is needed.
|
|
49
|
+
* @constant {number}
|
|
50
|
+
*/
|
|
16
51
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
52
|
+
/**
|
|
53
|
+
* Websocket message ID indicating a restart is needed.
|
|
54
|
+
* @constant {number}
|
|
55
|
+
*/
|
|
17
56
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
57
|
+
/**
|
|
58
|
+
* Websocket message ID indicating a cpu update.
|
|
59
|
+
* @constant {number}
|
|
60
|
+
*/
|
|
18
61
|
export const WS_ID_CPU_UPDATE = 3;
|
|
62
|
+
/**
|
|
63
|
+
* Websocket message ID indicating a memory update.
|
|
64
|
+
* @constant {number}
|
|
65
|
+
*/
|
|
19
66
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
67
|
+
/**
|
|
68
|
+
* Websocket message ID indicating an uptime update.
|
|
69
|
+
* @constant {number}
|
|
70
|
+
*/
|
|
20
71
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
72
|
+
/**
|
|
73
|
+
* Websocket message ID indicating a snackbar message.
|
|
74
|
+
* @constant {number}
|
|
75
|
+
*/
|
|
21
76
|
export const WS_ID_SNACKBAR = 6;
|
|
77
|
+
/**
|
|
78
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
79
|
+
* @constant {number}
|
|
80
|
+
*/
|
|
22
81
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
82
|
+
/**
|
|
83
|
+
* Websocket message ID indicating a state update.
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
23
86
|
export const WS_ID_STATEUPDATE = 8;
|
|
87
|
+
/**
|
|
88
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
89
|
+
* @constant {number}
|
|
90
|
+
*/
|
|
24
91
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
92
|
+
/**
|
|
93
|
+
* Websocket message ID indicating a shelly system update.
|
|
94
|
+
* check:
|
|
95
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
96
|
+
* perform:
|
|
97
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
98
|
+
* @constant {number}
|
|
99
|
+
*/
|
|
25
100
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
101
|
+
/**
|
|
102
|
+
* Websocket message ID indicating a shelly main update.
|
|
103
|
+
* check:
|
|
104
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
105
|
+
* perform:
|
|
106
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
107
|
+
* @constant {number}
|
|
108
|
+
*/
|
|
26
109
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
27
110
|
export class Frontend {
|
|
28
111
|
matterbridge;
|
|
@@ -40,7 +123,7 @@ export class Frontend {
|
|
|
40
123
|
memoryTimeout;
|
|
41
124
|
constructor(matterbridge) {
|
|
42
125
|
this.matterbridge = matterbridge;
|
|
43
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
126
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
44
127
|
}
|
|
45
128
|
set logLevel(logLevel) {
|
|
46
129
|
this.log.logLevel = logLevel;
|
|
@@ -48,13 +131,43 @@ export class Frontend {
|
|
|
48
131
|
async start(port = 8283) {
|
|
49
132
|
this.port = port;
|
|
50
133
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
134
|
+
// Initialize multer with the upload directory
|
|
51
135
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
52
136
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
53
137
|
const upload = multer({ dest: uploadDir });
|
|
138
|
+
// Create the express app that serves the frontend
|
|
54
139
|
this.expressApp = express();
|
|
140
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
141
|
+
/*
|
|
142
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
143
|
+
for (const method of methods) {
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
148
|
+
try {
|
|
149
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
150
|
+
return original(path, ...rest);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
*/
|
|
158
|
+
// Log all requests to the server for debugging
|
|
159
|
+
/*
|
|
160
|
+
this.expressApp.use((req, res, next) => {
|
|
161
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
162
|
+
next();
|
|
163
|
+
});
|
|
164
|
+
*/
|
|
165
|
+
// Serve static files from '/static' endpoint
|
|
55
166
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
56
167
|
if (!hasParameter('ssl')) {
|
|
168
|
+
// Create an HTTP server and attach the express app
|
|
57
169
|
this.httpServer = createServer(this.expressApp);
|
|
170
|
+
// Listen on the specified port
|
|
58
171
|
if (hasParameter('ingress')) {
|
|
59
172
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
60
173
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -68,6 +181,7 @@ export class Frontend {
|
|
|
68
181
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
69
182
|
});
|
|
70
183
|
}
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
185
|
this.httpServer.on('error', (error) => {
|
|
72
186
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
73
187
|
switch (error.code) {
|
|
@@ -83,6 +197,7 @@ export class Frontend {
|
|
|
83
197
|
});
|
|
84
198
|
}
|
|
85
199
|
else {
|
|
200
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
86
201
|
let cert;
|
|
87
202
|
try {
|
|
88
203
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -110,7 +225,9 @@ export class Frontend {
|
|
|
110
225
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
111
226
|
}
|
|
112
227
|
const serverOptions = { cert, key, ca };
|
|
228
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
113
229
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
230
|
+
// Listen on the specified port
|
|
114
231
|
if (hasParameter('ingress')) {
|
|
115
232
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
116
233
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -124,6 +241,7 @@ export class Frontend {
|
|
|
124
241
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
125
242
|
});
|
|
126
243
|
}
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
127
245
|
this.httpsServer.on('error', (error) => {
|
|
128
246
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
129
247
|
switch (error.code) {
|
|
@@ -140,16 +258,18 @@ export class Frontend {
|
|
|
140
258
|
}
|
|
141
259
|
if (this.initializeError)
|
|
142
260
|
return;
|
|
261
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
143
262
|
const wssPort = this.port;
|
|
144
263
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
145
264
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
146
265
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
147
266
|
const clientIp = request.socket.remoteAddress;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
267
|
+
// Set the global logger callback for the WebSocketServer
|
|
268
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
269
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
270
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
271
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
272
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
153
273
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
154
274
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
155
275
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -183,6 +303,7 @@ export class Frontend {
|
|
|
183
303
|
this.webSocketServer.on('error', (ws, error) => {
|
|
184
304
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
185
305
|
});
|
|
306
|
+
// Subscribe to cli events
|
|
186
307
|
const { cliEmitter } = await import('./cli.js');
|
|
187
308
|
cliEmitter.removeAllListeners();
|
|
188
309
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
@@ -194,6 +315,7 @@ export class Frontend {
|
|
|
194
315
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
195
316
|
this.wssSendCpuUpdate(cpuUsage);
|
|
196
317
|
});
|
|
318
|
+
// Endpoint to validate login code
|
|
197
319
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
198
320
|
const { password } = req.body;
|
|
199
321
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -212,23 +334,27 @@ export class Frontend {
|
|
|
212
334
|
this.log.warn('/api/login error wrong password');
|
|
213
335
|
res.json({ valid: false });
|
|
214
336
|
}
|
|
337
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
215
338
|
}
|
|
216
339
|
catch (error) {
|
|
217
340
|
this.log.error('/api/login error getting password');
|
|
218
341
|
res.json({ valid: false });
|
|
219
342
|
}
|
|
220
343
|
});
|
|
344
|
+
// Endpoint to provide health check for docker
|
|
221
345
|
this.expressApp.get('/health', (req, res) => {
|
|
222
346
|
this.log.debug('Express received /health');
|
|
223
347
|
const healthStatus = {
|
|
224
|
-
status: 'ok',
|
|
225
|
-
uptime: process.uptime(),
|
|
226
|
-
timestamp: new Date().toISOString(),
|
|
348
|
+
status: 'ok', // Indicate service is healthy
|
|
349
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
350
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
227
351
|
};
|
|
228
352
|
res.status(200).json(healthStatus);
|
|
229
353
|
});
|
|
354
|
+
// Endpoint to provide memory usage details
|
|
230
355
|
this.expressApp.get('/memory', async (req, res) => {
|
|
231
356
|
this.log.debug('Express received /memory');
|
|
357
|
+
// Memory usage from process
|
|
232
358
|
const memoryUsageRaw = process.memoryUsage();
|
|
233
359
|
const memoryUsage = {
|
|
234
360
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -237,10 +363,13 @@ export class Frontend {
|
|
|
237
363
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
238
364
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
239
365
|
};
|
|
366
|
+
// V8 heap statistics
|
|
240
367
|
const { default: v8 } = await import('node:v8');
|
|
241
368
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
242
369
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
370
|
+
// Format heapStats
|
|
243
371
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
372
|
+
// Format heapSpaces
|
|
244
373
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
245
374
|
...space,
|
|
246
375
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -258,19 +387,23 @@ export class Frontend {
|
|
|
258
387
|
};
|
|
259
388
|
res.status(200).json(memoryReport);
|
|
260
389
|
});
|
|
390
|
+
// Endpoint to provide settings
|
|
261
391
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
262
392
|
this.log.debug('The frontend sent /api/settings');
|
|
263
393
|
res.json(await this.getApiSettings());
|
|
264
394
|
});
|
|
395
|
+
// Endpoint to provide plugins
|
|
265
396
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
266
397
|
this.log.debug('The frontend sent /api/plugins');
|
|
267
398
|
res.json(this.getBaseRegisteredPlugins());
|
|
268
399
|
});
|
|
400
|
+
// Endpoint to provide devices
|
|
269
401
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
270
402
|
this.log.debug('The frontend sent /api/devices');
|
|
271
403
|
const devices = await this.getDevices();
|
|
272
404
|
res.json(devices);
|
|
273
405
|
});
|
|
406
|
+
// Endpoint to view the matterbridge log
|
|
274
407
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
275
408
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
276
409
|
try {
|
|
@@ -283,6 +416,7 @@ export class Frontend {
|
|
|
283
416
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
284
417
|
}
|
|
285
418
|
});
|
|
419
|
+
// Endpoint to view the matter.js log
|
|
286
420
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
287
421
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
288
422
|
try {
|
|
@@ -295,6 +429,7 @@ export class Frontend {
|
|
|
295
429
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
296
430
|
}
|
|
297
431
|
});
|
|
432
|
+
// Endpoint to view the shelly log
|
|
298
433
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
299
434
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
300
435
|
try {
|
|
@@ -307,6 +442,7 @@ export class Frontend {
|
|
|
307
442
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
308
443
|
}
|
|
309
444
|
});
|
|
445
|
+
// Endpoint to download the matterbridge log
|
|
310
446
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
311
447
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
312
448
|
try {
|
|
@@ -319,6 +455,7 @@ export class Frontend {
|
|
|
319
455
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
320
456
|
}
|
|
321
457
|
res.type('text/plain');
|
|
458
|
+
// res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
322
459
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
323
460
|
if (error) {
|
|
324
461
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
@@ -326,6 +463,7 @@ export class Frontend {
|
|
|
326
463
|
}
|
|
327
464
|
});
|
|
328
465
|
});
|
|
466
|
+
// Endpoint to download the matter log
|
|
329
467
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
330
468
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
331
469
|
try {
|
|
@@ -345,6 +483,7 @@ export class Frontend {
|
|
|
345
483
|
}
|
|
346
484
|
});
|
|
347
485
|
});
|
|
486
|
+
// Endpoint to download the shelly log
|
|
348
487
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
349
488
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
350
489
|
try {
|
|
@@ -364,6 +503,7 @@ export class Frontend {
|
|
|
364
503
|
}
|
|
365
504
|
});
|
|
366
505
|
});
|
|
506
|
+
// Endpoint to download the matterbridge storage directory
|
|
367
507
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
368
508
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
369
509
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -374,6 +514,7 @@ export class Frontend {
|
|
|
374
514
|
}
|
|
375
515
|
});
|
|
376
516
|
});
|
|
517
|
+
// Endpoint to download the matter storage file
|
|
377
518
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
378
519
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
379
520
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -384,6 +525,7 @@ export class Frontend {
|
|
|
384
525
|
}
|
|
385
526
|
});
|
|
386
527
|
});
|
|
528
|
+
// Endpoint to download the matterbridge plugin directory
|
|
387
529
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
388
530
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
389
531
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -394,9 +536,11 @@ export class Frontend {
|
|
|
394
536
|
}
|
|
395
537
|
});
|
|
396
538
|
});
|
|
539
|
+
// Endpoint to download the matterbridge plugin config files
|
|
397
540
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
398
541
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
399
542
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
543
|
+
// await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
400
544
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
401
545
|
if (error) {
|
|
402
546
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -404,6 +548,7 @@ export class Frontend {
|
|
|
404
548
|
}
|
|
405
549
|
});
|
|
406
550
|
});
|
|
551
|
+
// Endpoint to download the matterbridge plugin config files
|
|
407
552
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
408
553
|
this.log.debug('The frontend sent /api/download-backup');
|
|
409
554
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -413,6 +558,7 @@ export class Frontend {
|
|
|
413
558
|
}
|
|
414
559
|
});
|
|
415
560
|
});
|
|
561
|
+
// Endpoint to upload a package
|
|
416
562
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
417
563
|
const { filename } = req.body;
|
|
418
564
|
const file = req.file;
|
|
@@ -422,10 +568,13 @@ export class Frontend {
|
|
|
422
568
|
return;
|
|
423
569
|
}
|
|
424
570
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
571
|
+
// Define the path where the plugin file will be saved
|
|
425
572
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
426
573
|
try {
|
|
574
|
+
// Move the uploaded file to the specified path
|
|
427
575
|
await fs.rename(file.path, filePath);
|
|
428
576
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
577
|
+
// Install the plugin package
|
|
429
578
|
if (filename.endsWith('.tgz')) {
|
|
430
579
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
431
580
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
@@ -444,6 +593,7 @@ export class Frontend {
|
|
|
444
593
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
445
594
|
}
|
|
446
595
|
});
|
|
596
|
+
// Fallback for routing (must be the last route)
|
|
447
597
|
this.expressApp.use((req, res) => {
|
|
448
598
|
this.log.debug('The frontend sent:', req.url);
|
|
449
599
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -451,24 +601,29 @@ export class Frontend {
|
|
|
451
601
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
452
602
|
}
|
|
453
603
|
async stop() {
|
|
604
|
+
// Close the http server
|
|
454
605
|
if (this.httpServer) {
|
|
455
606
|
this.httpServer.close();
|
|
456
607
|
this.httpServer.removeAllListeners();
|
|
457
608
|
this.httpServer = undefined;
|
|
458
609
|
this.log.debug('Frontend http server closed successfully');
|
|
459
610
|
}
|
|
611
|
+
// Close the https server
|
|
460
612
|
if (this.httpsServer) {
|
|
461
613
|
this.httpsServer.close();
|
|
462
614
|
this.httpsServer.removeAllListeners();
|
|
463
615
|
this.httpsServer = undefined;
|
|
464
616
|
this.log.debug('Frontend https server closed successfully');
|
|
465
617
|
}
|
|
618
|
+
// Remove listeners from the express app
|
|
466
619
|
if (this.expressApp) {
|
|
467
620
|
this.expressApp.removeAllListeners();
|
|
468
621
|
this.expressApp = undefined;
|
|
469
622
|
this.log.debug('Frontend app closed successfully');
|
|
470
623
|
}
|
|
624
|
+
// Close the WebSocket server
|
|
471
625
|
if (this.webSocketServer) {
|
|
626
|
+
// Close all active connections
|
|
472
627
|
this.webSocketServer.clients.forEach((client) => {
|
|
473
628
|
if (client.readyState === WebSocket.OPEN) {
|
|
474
629
|
client.close();
|
|
@@ -485,6 +640,7 @@ export class Frontend {
|
|
|
485
640
|
this.webSocketServer = undefined;
|
|
486
641
|
}
|
|
487
642
|
}
|
|
643
|
+
// Function to format bytes to KB, MB, or GB
|
|
488
644
|
formatMemoryUsage = (bytes) => {
|
|
489
645
|
if (bytes >= 1024 ** 3) {
|
|
490
646
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -496,6 +652,7 @@ export class Frontend {
|
|
|
496
652
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
497
653
|
}
|
|
498
654
|
};
|
|
655
|
+
// Function to format system uptime with only the most significant unit
|
|
499
656
|
formatOsUpTime = (seconds) => {
|
|
500
657
|
if (seconds >= 86400) {
|
|
501
658
|
const days = Math.floor(seconds / 86400);
|
|
@@ -511,8 +668,13 @@ export class Frontend {
|
|
|
511
668
|
}
|
|
512
669
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
513
670
|
};
|
|
671
|
+
/**
|
|
672
|
+
* Retrieves the api settings data.
|
|
673
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
674
|
+
*/
|
|
514
675
|
async getApiSettings() {
|
|
515
676
|
const { lastCpuUsage } = await import('./cli.js');
|
|
677
|
+
// Update the system information
|
|
516
678
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
517
679
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
518
680
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -521,6 +683,7 @@ export class Frontend {
|
|
|
521
683
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
522
684
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
523
685
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
686
|
+
// Update the matterbridge information
|
|
524
687
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
525
688
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
526
689
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -539,6 +702,11 @@ export class Frontend {
|
|
|
539
702
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
540
703
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
541
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Retrieves the reachable attribute.
|
|
707
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
708
|
+
* @returns {boolean} The reachable attribute.
|
|
709
|
+
*/
|
|
542
710
|
getReachability(device) {
|
|
543
711
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
544
712
|
return false;
|
|
@@ -563,13 +731,20 @@ export class Frontend {
|
|
|
563
731
|
}
|
|
564
732
|
return;
|
|
565
733
|
};
|
|
734
|
+
// Root endpoint
|
|
566
735
|
if (device.hasClusterServer(PowerSource.Cluster.id))
|
|
567
736
|
return powerSource(device);
|
|
737
|
+
// Child endpoints
|
|
568
738
|
for (const child of device.getChildEndpoints()) {
|
|
569
739
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
570
740
|
return powerSource(child);
|
|
571
741
|
}
|
|
572
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Retrieves the cluster text description from a given device.
|
|
745
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
746
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
747
|
+
*/
|
|
573
748
|
getClusterTextFromDevice(device) {
|
|
574
749
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
575
750
|
return '';
|
|
@@ -611,6 +786,7 @@ export class Frontend {
|
|
|
611
786
|
let attributes = '';
|
|
612
787
|
let supportedModes = [];
|
|
613
788
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
789
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
614
790
|
if (typeof attributeValue === 'undefined')
|
|
615
791
|
return;
|
|
616
792
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -702,8 +878,13 @@ export class Frontend {
|
|
|
702
878
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
703
879
|
attributes += `${getUserLabel(device)} `;
|
|
704
880
|
});
|
|
881
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
705
882
|
return attributes.trimStart().trimEnd();
|
|
706
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
886
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
887
|
+
*/
|
|
707
888
|
getBaseRegisteredPlugins() {
|
|
708
889
|
const baseRegisteredPlugins = [];
|
|
709
890
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -742,11 +923,18 @@ export class Frontend {
|
|
|
742
923
|
}
|
|
743
924
|
return baseRegisteredPlugins;
|
|
744
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Retrieves the devices from Matterbridge.
|
|
928
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
929
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
|
|
930
|
+
*/
|
|
745
931
|
async getDevices(pluginName) {
|
|
746
932
|
const devices = [];
|
|
747
933
|
this.matterbridge.devices.forEach(async (device) => {
|
|
934
|
+
// Filter by pluginName if provided
|
|
748
935
|
if (pluginName && pluginName !== device.plugin)
|
|
749
936
|
return;
|
|
937
|
+
// Check if the device has the required properties
|
|
750
938
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
751
939
|
return;
|
|
752
940
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -766,6 +954,13 @@ export class Frontend {
|
|
|
766
954
|
});
|
|
767
955
|
return devices;
|
|
768
956
|
}
|
|
957
|
+
/**
|
|
958
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
959
|
+
*
|
|
960
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
961
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
962
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
963
|
+
*/
|
|
769
964
|
async wsMessageHandler(client, message) {
|
|
770
965
|
let data;
|
|
771
966
|
try {
|
|
@@ -812,8 +1007,10 @@ export class Frontend {
|
|
|
812
1007
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
813
1008
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
814
1009
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1010
|
+
// The install comes from InstallPlugins
|
|
815
1011
|
this.matterbridge.plugins.add(packageName).then((plugin) => {
|
|
816
1012
|
if (plugin) {
|
|
1013
|
+
// The plugin is not registered
|
|
817
1014
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
818
1015
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
|
|
819
1016
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
@@ -821,6 +1018,7 @@ export class Frontend {
|
|
|
821
1018
|
});
|
|
822
1019
|
}
|
|
823
1020
|
else {
|
|
1021
|
+
// The plugin is already registered
|
|
824
1022
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
825
1023
|
this.wssSendRefreshRequired('plugins');
|
|
826
1024
|
this.wssSendRestartRequired();
|
|
@@ -828,6 +1026,7 @@ export class Frontend {
|
|
|
828
1026
|
});
|
|
829
1027
|
}
|
|
830
1028
|
else {
|
|
1029
|
+
// The package is matterbridge
|
|
831
1030
|
if (this.matterbridge.restartMode !== '') {
|
|
832
1031
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
833
1032
|
this.matterbridge.shutdownProcess();
|
|
@@ -849,6 +1048,7 @@ export class Frontend {
|
|
|
849
1048
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
850
1049
|
return;
|
|
851
1050
|
}
|
|
1051
|
+
// The package is a plugin
|
|
852
1052
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
853
1053
|
if (plugin) {
|
|
854
1054
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -857,6 +1057,7 @@ export class Frontend {
|
|
|
857
1057
|
this.wssSendRefreshRequired('plugins');
|
|
858
1058
|
this.wssSendRefreshRequired('devices');
|
|
859
1059
|
}
|
|
1060
|
+
// Uninstall the package
|
|
860
1061
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
861
1062
|
this.matterbridge
|
|
862
1063
|
.spawnCommand('npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1133,6 +1334,7 @@ export class Frontend {
|
|
|
1133
1334
|
});
|
|
1134
1335
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1135
1336
|
deviceTypes = [];
|
|
1337
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1136
1338
|
const name = childEndpoint.endpoint?.id;
|
|
1137
1339
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1138
1340
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1246,22 +1448,22 @@ export class Frontend {
|
|
|
1246
1448
|
if (isValidString(data.params.value, 4)) {
|
|
1247
1449
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1248
1450
|
if (data.params.value === 'Debug') {
|
|
1249
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1451
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1250
1452
|
}
|
|
1251
1453
|
else if (data.params.value === 'Info') {
|
|
1252
|
-
await this.matterbridge.setLogLevel("info");
|
|
1454
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1253
1455
|
}
|
|
1254
1456
|
else if (data.params.value === 'Notice') {
|
|
1255
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1457
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1256
1458
|
}
|
|
1257
1459
|
else if (data.params.value === 'Warn') {
|
|
1258
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1460
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1259
1461
|
}
|
|
1260
1462
|
else if (data.params.value === 'Error') {
|
|
1261
|
-
await this.matterbridge.setLogLevel("error");
|
|
1463
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1262
1464
|
}
|
|
1263
1465
|
else if (data.params.value === 'Fatal') {
|
|
1264
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1466
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1265
1467
|
}
|
|
1266
1468
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1267
1469
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1272,6 +1474,7 @@ export class Frontend {
|
|
|
1272
1474
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1273
1475
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1274
1476
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1477
|
+
// Create the file logger for matterbridge
|
|
1275
1478
|
if (data.params.value)
|
|
1276
1479
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1277
1480
|
else
|
|
@@ -1427,15 +1630,19 @@ export class Frontend {
|
|
|
1427
1630
|
return;
|
|
1428
1631
|
}
|
|
1429
1632
|
const config = plugin.configJson;
|
|
1633
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1430
1634
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1635
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1431
1636
|
if (select === 'serial')
|
|
1432
1637
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1433
1638
|
if (select === 'name')
|
|
1434
1639
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1435
1640
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1641
|
+
// Remove postfix from the serial if it exists
|
|
1436
1642
|
if (config.postfix) {
|
|
1437
1643
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1438
1644
|
}
|
|
1645
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1439
1646
|
if (isValidArray(config.whiteList, 1)) {
|
|
1440
1647
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1441
1648
|
config.whiteList.push(data.params.serial);
|
|
@@ -1444,6 +1651,7 @@ export class Frontend {
|
|
|
1444
1651
|
config.whiteList.push(data.params.name);
|
|
1445
1652
|
}
|
|
1446
1653
|
}
|
|
1654
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1447
1655
|
if (isValidArray(config.blackList, 1)) {
|
|
1448
1656
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1449
1657
|
config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
|
|
@@ -1473,7 +1681,9 @@ export class Frontend {
|
|
|
1473
1681
|
return;
|
|
1474
1682
|
}
|
|
1475
1683
|
const config = plugin.configJson;
|
|
1684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1476
1685
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1686
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1477
1687
|
if (select === 'serial')
|
|
1478
1688
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1479
1689
|
if (select === 'name')
|
|
@@ -1482,6 +1692,7 @@ export class Frontend {
|
|
|
1482
1692
|
if (config.postfix) {
|
|
1483
1693
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1484
1694
|
}
|
|
1695
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1485
1696
|
if (isValidArray(config.whiteList, 1)) {
|
|
1486
1697
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1487
1698
|
config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
|
|
@@ -1490,6 +1701,7 @@ export class Frontend {
|
|
|
1490
1701
|
config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
|
|
1491
1702
|
}
|
|
1492
1703
|
}
|
|
1704
|
+
// Add the serial to the blackList
|
|
1493
1705
|
if (isValidArray(config.blackList)) {
|
|
1494
1706
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1495
1707
|
config.blackList.push(data.params.serial);
|
|
@@ -1522,114 +1734,214 @@ export class Frontend {
|
|
|
1522
1734
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1523
1735
|
}
|
|
1524
1736
|
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1739
|
+
*
|
|
1740
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1741
|
+
* @param {string} time - The time string of the message
|
|
1742
|
+
* @param {string} name - The logger name of the message
|
|
1743
|
+
* @param {string} message - The content of the message.
|
|
1744
|
+
*/
|
|
1525
1745
|
wssSendMessage(level, time, name, message) {
|
|
1526
1746
|
if (!level || !time || !name || !message)
|
|
1527
1747
|
return;
|
|
1748
|
+
// Remove ANSI escape codes from the message
|
|
1749
|
+
// eslint-disable-next-line no-control-regex
|
|
1528
1750
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1751
|
+
// Remove leading asterisks from the message
|
|
1529
1752
|
message = message.replace(/^\*+/, '');
|
|
1753
|
+
// Replace all occurrences of \t and \n
|
|
1530
1754
|
message = message.replace(/[\t\n]/g, '');
|
|
1755
|
+
// Remove non-printable characters
|
|
1756
|
+
// eslint-disable-next-line no-control-regex
|
|
1531
1757
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1758
|
+
// Replace all occurrences of \" with "
|
|
1532
1759
|
message = message.replace(/\\"/g, '"');
|
|
1760
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1533
1761
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1762
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1534
1763
|
const maxContinuousLength = 100;
|
|
1535
1764
|
const keepStartLength = 20;
|
|
1536
1765
|
const keepEndLength = 20;
|
|
1766
|
+
// Split the message into words
|
|
1537
1767
|
message = message
|
|
1538
1768
|
.split(' ')
|
|
1539
1769
|
.map((word) => {
|
|
1770
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1540
1771
|
if (word.length > maxContinuousLength) {
|
|
1541
1772
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1542
1773
|
}
|
|
1543
1774
|
return word;
|
|
1544
1775
|
})
|
|
1545
1776
|
.join(' ');
|
|
1777
|
+
// Send the message to all connected clients
|
|
1546
1778
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1547
1779
|
if (client.readyState === WebSocket.OPEN) {
|
|
1548
1780
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1549
1781
|
}
|
|
1550
1782
|
});
|
|
1551
1783
|
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1786
|
+
*
|
|
1787
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1788
|
+
* possible values:
|
|
1789
|
+
* - 'matterbridgeLatestVersion'
|
|
1790
|
+
* - 'matterbridgeAdvertise'
|
|
1791
|
+
* - 'online'
|
|
1792
|
+
* - 'offline'
|
|
1793
|
+
* - 'reachability'
|
|
1794
|
+
* - 'settings'
|
|
1795
|
+
* - 'plugins'
|
|
1796
|
+
* - 'pluginsRestart'
|
|
1797
|
+
* - 'devices'
|
|
1798
|
+
* - 'fabrics'
|
|
1799
|
+
* - 'sessions'
|
|
1800
|
+
*/
|
|
1552
1801
|
wssSendRefreshRequired(changed = null) {
|
|
1553
1802
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1803
|
+
// Send the message to all connected clients
|
|
1554
1804
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1555
1805
|
if (client.readyState === WebSocket.OPEN) {
|
|
1556
1806
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1557
1807
|
}
|
|
1558
1808
|
});
|
|
1559
1809
|
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1812
|
+
*
|
|
1813
|
+
*/
|
|
1560
1814
|
wssSendRestartRequired(snackbar = true) {
|
|
1561
1815
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1562
1816
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1563
1817
|
if (snackbar === true)
|
|
1564
1818
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1819
|
+
// Send the message to all connected clients
|
|
1565
1820
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1566
1821
|
if (client.readyState === WebSocket.OPEN) {
|
|
1567
1822
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1568
1823
|
}
|
|
1569
1824
|
});
|
|
1570
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1828
|
+
*
|
|
1829
|
+
*/
|
|
1571
1830
|
wssSendUpdateRequired() {
|
|
1572
1831
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1573
1832
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1833
|
+
// Send the message to all connected clients
|
|
1574
1834
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1575
1835
|
if (client.readyState === WebSocket.OPEN) {
|
|
1576
1836
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1577
1837
|
}
|
|
1578
1838
|
});
|
|
1579
1839
|
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Sends a cpu update message to all connected clients.
|
|
1842
|
+
*
|
|
1843
|
+
*/
|
|
1580
1844
|
wssSendCpuUpdate(cpuUsage) {
|
|
1581
1845
|
if (hasParameter('debug'))
|
|
1582
1846
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1847
|
+
// Send the message to all connected clients
|
|
1583
1848
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1584
1849
|
if (client.readyState === WebSocket.OPEN) {
|
|
1585
1850
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1586
1851
|
}
|
|
1587
1852
|
});
|
|
1588
1853
|
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Sends a memory update message to all connected clients.
|
|
1856
|
+
*
|
|
1857
|
+
*/
|
|
1589
1858
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1590
1859
|
if (hasParameter('debug'))
|
|
1591
1860
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1861
|
+
// Send the message to all connected clients
|
|
1592
1862
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1593
1863
|
if (client.readyState === WebSocket.OPEN) {
|
|
1594
1864
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1595
1865
|
}
|
|
1596
1866
|
});
|
|
1597
1867
|
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Sends an uptime update message to all connected clients.
|
|
1870
|
+
*
|
|
1871
|
+
*/
|
|
1598
1872
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1599
1873
|
if (hasParameter('debug'))
|
|
1600
1874
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1875
|
+
// Send the message to all connected clients
|
|
1601
1876
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1602
1877
|
if (client.readyState === WebSocket.OPEN) {
|
|
1603
1878
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1604
1879
|
}
|
|
1605
1880
|
});
|
|
1606
1881
|
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Sends an open snackbar message to all connected clients.
|
|
1884
|
+
* @param {string} message - The message to send.
|
|
1885
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
1886
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
1887
|
+
*
|
|
1888
|
+
*/
|
|
1607
1889
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1608
1890
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1891
|
+
// Send the message to all connected clients
|
|
1609
1892
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1610
1893
|
if (client.readyState === WebSocket.OPEN) {
|
|
1611
1894
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1612
1895
|
}
|
|
1613
1896
|
});
|
|
1614
1897
|
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Sends a close snackbar message to all connected clients.
|
|
1900
|
+
* @param {string} message - The message to send.
|
|
1901
|
+
*
|
|
1902
|
+
*/
|
|
1615
1903
|
wssSendCloseSnackbarMessage(message) {
|
|
1616
1904
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
1905
|
+
// Send the message to all connected clients
|
|
1617
1906
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1618
1907
|
if (client.readyState === WebSocket.OPEN) {
|
|
1619
1908
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1620
1909
|
}
|
|
1621
1910
|
});
|
|
1622
1911
|
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
1914
|
+
*
|
|
1915
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
1916
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
1917
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
1918
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
1919
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
1920
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
1921
|
+
*
|
|
1922
|
+
* @remarks
|
|
1923
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
1924
|
+
* with the updated attribute information.
|
|
1925
|
+
*/
|
|
1623
1926
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1624
1927
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
1928
|
+
// Send the message to all connected clients
|
|
1625
1929
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1626
1930
|
if (client.readyState === WebSocket.OPEN) {
|
|
1627
1931
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1628
1932
|
}
|
|
1629
1933
|
});
|
|
1630
1934
|
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Sends a message to all connected clients.
|
|
1937
|
+
* @param {number} id - The message id.
|
|
1938
|
+
* @param {string} method - The message method.
|
|
1939
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
1940
|
+
*
|
|
1941
|
+
*/
|
|
1631
1942
|
wssBroadcastMessage(id, method, params) {
|
|
1632
1943
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
1944
|
+
// Send the message to all connected clients
|
|
1633
1945
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1634
1946
|
if (client.readyState === WebSocket.OPEN) {
|
|
1635
1947
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1637,3 +1949,4 @@ export class Frontend {
|
|
|
1637
1949
|
});
|
|
1638
1950
|
}
|
|
1639
1951
|
}
|
|
1952
|
+
//# sourceMappingURL=frontend.js.map
|