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