matterbridge 2.2.0-dev.4 → 2.2.0-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/dist/cli.d.ts +28 -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.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -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 +109 -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 +172 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +265 -22
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -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.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -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.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -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.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -1
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +410 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +718 -47
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +148 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +827 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +123 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +118 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +159 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +162 -23
- package/dist/matterbridgePlatform.js.map +1 -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 +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +229 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +131 -4
- package/dist/shelly.js.map +1 -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.map +1 -0
- package/dist/update.js +45 -0
- package/dist/update.js.map +1 -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.map +1 -0
- package/dist/utils/copyDirectory.js +37 -1
- package/dist/utils/copyDirectory.js.map +1 -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.map +1 -0
- package/dist/utils/deepCopy.js +40 -0
- package/dist/utils/deepCopy.js.map +1 -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.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +86 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +41 -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.f87f07b6.js → main.f60aae10.js} +8 -8
- package/frontend/build/static/js/main.f60aae10.js.map +1 -0
- package/npm-shrinkwrap.json +44 -44
- package/package.json +2 -2
- package/frontend/build/static/js/main.f87f07b6.js.map +0 -1
- /package/frontend/build/static/js/{main.f87f07b6.js.LICENSE.txt → main.f60aae10.js.LICENSE.txt} +0 -0
package/dist/frontend.js
CHANGED
|
@@ -1,4 +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
|
|
1
24
|
import { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat } from '@matter/main';
|
|
25
|
+
// Node modules
|
|
2
26
|
import { createServer } from 'node:http';
|
|
3
27
|
import https from 'https';
|
|
4
28
|
import express from 'express';
|
|
@@ -6,19 +30,70 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
6
30
|
import os from 'node:os';
|
|
7
31
|
import path from 'node:path';
|
|
8
32
|
import { promises as fs } from 'node:fs';
|
|
33
|
+
// AnsiLogger module
|
|
9
34
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from './logger/export.js';
|
|
35
|
+
// Matterbridge
|
|
10
36
|
import { createZip, deepCopy, isValidNumber, isValidObject, isValidString } from './utils/export.js';
|
|
11
37
|
import { plg } from './matterbridgeTypes.js';
|
|
12
38
|
import { hasParameter } from './utils/export.js';
|
|
39
|
+
/**
|
|
40
|
+
* Websocket message ID for logging.
|
|
41
|
+
* @constant {number}
|
|
42
|
+
*/
|
|
13
43
|
export const WS_ID_LOG = 0;
|
|
44
|
+
/**
|
|
45
|
+
* Websocket message ID indicating a refresh is needed.
|
|
46
|
+
* @constant {number}
|
|
47
|
+
*/
|
|
14
48
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
49
|
+
/**
|
|
50
|
+
* Websocket message ID indicating a restart is needed.
|
|
51
|
+
* @constant {number}
|
|
52
|
+
*/
|
|
15
53
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
54
|
+
/**
|
|
55
|
+
* Websocket message ID indicating a cpu update.
|
|
56
|
+
* @constant {number}
|
|
57
|
+
*/
|
|
16
58
|
export const WS_ID_CPU_UPDATE = 3;
|
|
59
|
+
/**
|
|
60
|
+
* Websocket message ID indicating a memory update.
|
|
61
|
+
* @constant {number}
|
|
62
|
+
*/
|
|
17
63
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
64
|
+
/**
|
|
65
|
+
* Websocket message ID indicating an uptime update.
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
18
68
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
* @constant {number}
|
|
72
|
+
*/
|
|
19
73
|
export const WS_ID_SNACKBAR = 6;
|
|
74
|
+
/**
|
|
75
|
+
* Websocket message ID indicating a shelly system update.
|
|
76
|
+
* check:
|
|
77
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
78
|
+
* perform:
|
|
79
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
80
|
+
* @constant {number}
|
|
81
|
+
*/
|
|
20
82
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
83
|
+
/**
|
|
84
|
+
* Websocket message ID indicating a shelly main update.
|
|
85
|
+
* check:
|
|
86
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
87
|
+
* perform:
|
|
88
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
89
|
+
* @constant {number}
|
|
90
|
+
*/
|
|
21
91
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
92
|
+
/**
|
|
93
|
+
* Initializes the frontend of Matterbridge.
|
|
94
|
+
*
|
|
95
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
96
|
+
*/
|
|
22
97
|
export class Frontend {
|
|
23
98
|
matterbridge;
|
|
24
99
|
log;
|
|
@@ -35,7 +110,7 @@ export class Frontend {
|
|
|
35
110
|
memoryTimeout;
|
|
36
111
|
constructor(matterbridge) {
|
|
37
112
|
this.matterbridge = matterbridge;
|
|
38
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
113
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
39
114
|
}
|
|
40
115
|
set logLevel(logLevel) {
|
|
41
116
|
this.log.logLevel = logLevel;
|
|
@@ -43,10 +118,21 @@ export class Frontend {
|
|
|
43
118
|
async start(port = 8283) {
|
|
44
119
|
this.port = port;
|
|
45
120
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
121
|
+
// Create the express app that serves the frontend
|
|
46
122
|
this.expressApp = express();
|
|
123
|
+
// Log all requests to the server for debugging
|
|
124
|
+
/*
|
|
125
|
+
this.expressApp.use((req, res, next) => {
|
|
126
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
127
|
+
next();
|
|
128
|
+
});
|
|
129
|
+
*/
|
|
130
|
+
// Serve static files from '/static' endpoint
|
|
47
131
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
48
132
|
if (!hasParameter('ssl')) {
|
|
133
|
+
// Create an HTTP server and attach the express app
|
|
49
134
|
this.httpServer = createServer(this.expressApp);
|
|
135
|
+
// Listen on the specified port
|
|
50
136
|
if (hasParameter('ingress')) {
|
|
51
137
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
52
138
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -60,6 +146,7 @@ export class Frontend {
|
|
|
60
146
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
61
147
|
});
|
|
62
148
|
}
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
150
|
this.httpServer.on('error', (error) => {
|
|
64
151
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
65
152
|
switch (error.code) {
|
|
@@ -75,6 +162,7 @@ export class Frontend {
|
|
|
75
162
|
});
|
|
76
163
|
}
|
|
77
164
|
else {
|
|
165
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
78
166
|
let cert;
|
|
79
167
|
try {
|
|
80
168
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -102,7 +190,9 @@ export class Frontend {
|
|
|
102
190
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
103
191
|
}
|
|
104
192
|
const serverOptions = { cert, key, ca };
|
|
193
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
105
194
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
195
|
+
// Listen on the specified port
|
|
106
196
|
if (hasParameter('ingress')) {
|
|
107
197
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
108
198
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -116,6 +206,7 @@ export class Frontend {
|
|
|
116
206
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
117
207
|
});
|
|
118
208
|
}
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
210
|
this.httpsServer.on('error', (error) => {
|
|
120
211
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
121
212
|
switch (error.code) {
|
|
@@ -132,16 +223,18 @@ export class Frontend {
|
|
|
132
223
|
}
|
|
133
224
|
if (this.initializeError)
|
|
134
225
|
return;
|
|
226
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
135
227
|
const wssPort = this.port;
|
|
136
228
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
137
229
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
138
230
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
139
231
|
const clientIp = request.socket.remoteAddress;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
232
|
+
// Set the global logger callback for the WebSocketServer
|
|
233
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
234
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
235
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
236
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
237
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
145
238
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
146
239
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
147
240
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -175,6 +268,7 @@ export class Frontend {
|
|
|
175
268
|
this.webSocketServer.on('error', (ws, error) => {
|
|
176
269
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
177
270
|
});
|
|
271
|
+
// Subscribe to cli events
|
|
178
272
|
const { cliEmitter } = await import('./cli.js');
|
|
179
273
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
180
274
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -185,6 +279,7 @@ export class Frontend {
|
|
|
185
279
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
186
280
|
this.wssSendCpuUpdate(cpuUsage);
|
|
187
281
|
});
|
|
282
|
+
// Endpoint to validate login code
|
|
188
283
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
189
284
|
const { password } = req.body;
|
|
190
285
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -203,23 +298,27 @@ export class Frontend {
|
|
|
203
298
|
this.log.warn('/api/login error wrong password');
|
|
204
299
|
res.json({ valid: false });
|
|
205
300
|
}
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
206
302
|
}
|
|
207
303
|
catch (error) {
|
|
208
304
|
this.log.error('/api/login error getting password');
|
|
209
305
|
res.json({ valid: false });
|
|
210
306
|
}
|
|
211
307
|
});
|
|
308
|
+
// Endpoint to provide health check
|
|
212
309
|
this.expressApp.get('/health', (req, res) => {
|
|
213
310
|
this.log.debug('Express received /health');
|
|
214
311
|
const healthStatus = {
|
|
215
|
-
status: 'ok',
|
|
216
|
-
uptime: process.uptime(),
|
|
217
|
-
timestamp: new Date().toISOString(),
|
|
312
|
+
status: 'ok', // Indicate service is healthy
|
|
313
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
314
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
218
315
|
};
|
|
219
316
|
res.status(200).json(healthStatus);
|
|
220
317
|
});
|
|
318
|
+
// Endpoint to provide memory usage details
|
|
221
319
|
this.expressApp.get('/memory', async (req, res) => {
|
|
222
320
|
this.log.debug('Express received /memory');
|
|
321
|
+
// Memory usage from process
|
|
223
322
|
const memoryUsageRaw = process.memoryUsage();
|
|
224
323
|
const memoryUsage = {
|
|
225
324
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -228,10 +327,13 @@ export class Frontend {
|
|
|
228
327
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
229
328
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
230
329
|
};
|
|
330
|
+
// V8 heap statistics
|
|
231
331
|
const { default: v8 } = await import('node:v8');
|
|
232
332
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
233
333
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
334
|
+
// Format heapStats
|
|
234
335
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
336
|
+
// Format heapSpaces
|
|
235
337
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
236
338
|
...space,
|
|
237
339
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -249,6 +351,7 @@ export class Frontend {
|
|
|
249
351
|
};
|
|
250
352
|
res.status(200).json(memoryReport);
|
|
251
353
|
});
|
|
354
|
+
// Endpoint to start advertising the server node
|
|
252
355
|
this.expressApp.get('/api/advertise', express.json(), async (req, res) => {
|
|
253
356
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
254
357
|
if (pairingCodes) {
|
|
@@ -259,18 +362,22 @@ export class Frontend {
|
|
|
259
362
|
res.status(500).json({ error: 'Failed to generate pairing codes' });
|
|
260
363
|
}
|
|
261
364
|
});
|
|
365
|
+
// Endpoint to provide settings
|
|
262
366
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
263
367
|
this.log.debug('The frontend sent /api/settings');
|
|
264
368
|
res.json(await this.getApiSettings());
|
|
265
369
|
});
|
|
370
|
+
// Endpoint to provide plugins
|
|
266
371
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
267
372
|
this.log.debug('The frontend sent /api/plugins');
|
|
268
373
|
res.json(this.getBaseRegisteredPlugins());
|
|
269
374
|
});
|
|
375
|
+
// Endpoint to provide devices
|
|
270
376
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
271
377
|
this.log.debug('The frontend sent /api/devices');
|
|
272
378
|
const devices = [];
|
|
273
379
|
this.matterbridge.devices.forEach(async (device) => {
|
|
380
|
+
// Check if the device has the required properties
|
|
274
381
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
275
382
|
return;
|
|
276
383
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -288,6 +395,7 @@ export class Frontend {
|
|
|
288
395
|
});
|
|
289
396
|
res.json(devices);
|
|
290
397
|
});
|
|
398
|
+
// Endpoint to provide the cluster servers of the devices
|
|
291
399
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
292
400
|
const selectedPluginName = req.params.selectedPluginName;
|
|
293
401
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -360,6 +468,7 @@ export class Frontend {
|
|
|
360
468
|
});
|
|
361
469
|
res.json(data);
|
|
362
470
|
});
|
|
471
|
+
// Endpoint to view the log
|
|
363
472
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
364
473
|
this.log.debug('The frontend sent /api/log');
|
|
365
474
|
try {
|
|
@@ -372,10 +481,12 @@ export class Frontend {
|
|
|
372
481
|
res.status(500).send('Error reading log file');
|
|
373
482
|
}
|
|
374
483
|
});
|
|
484
|
+
// Endpoint to download the matterbridge log
|
|
375
485
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
376
486
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
377
487
|
try {
|
|
378
488
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
489
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
379
490
|
}
|
|
380
491
|
catch (error) {
|
|
381
492
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -387,10 +498,12 @@ export class Frontend {
|
|
|
387
498
|
}
|
|
388
499
|
});
|
|
389
500
|
});
|
|
501
|
+
// Endpoint to download the matter log
|
|
390
502
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
391
503
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
392
504
|
try {
|
|
393
505
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
506
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
394
507
|
}
|
|
395
508
|
catch (error) {
|
|
396
509
|
fs.appendFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -402,6 +515,7 @@ export class Frontend {
|
|
|
402
515
|
}
|
|
403
516
|
});
|
|
404
517
|
});
|
|
518
|
+
// Endpoint to download the matter storage file
|
|
405
519
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
406
520
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
407
521
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -412,6 +526,7 @@ export class Frontend {
|
|
|
412
526
|
}
|
|
413
527
|
});
|
|
414
528
|
});
|
|
529
|
+
// Endpoint to download the matterbridge storage directory
|
|
415
530
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
416
531
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
417
532
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -422,6 +537,7 @@ export class Frontend {
|
|
|
422
537
|
}
|
|
423
538
|
});
|
|
424
539
|
});
|
|
540
|
+
// Endpoint to download the matterbridge plugin directory
|
|
425
541
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
426
542
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
427
543
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -432,9 +548,11 @@ export class Frontend {
|
|
|
432
548
|
}
|
|
433
549
|
});
|
|
434
550
|
});
|
|
551
|
+
// Endpoint to download the matterbridge plugin config files
|
|
435
552
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
436
553
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
437
554
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
555
|
+
// 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')));
|
|
438
556
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
439
557
|
if (error) {
|
|
440
558
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -442,6 +560,7 @@ export class Frontend {
|
|
|
442
560
|
}
|
|
443
561
|
});
|
|
444
562
|
});
|
|
563
|
+
// Endpoint to download the matterbridge plugin config files
|
|
445
564
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
446
565
|
this.log.debug('The frontend sent /api/download-backup');
|
|
447
566
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -451,6 +570,7 @@ export class Frontend {
|
|
|
451
570
|
}
|
|
452
571
|
});
|
|
453
572
|
});
|
|
573
|
+
// Endpoint to receive commands
|
|
454
574
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
455
575
|
const command = req.params.command;
|
|
456
576
|
let param = req.params.param;
|
|
@@ -460,13 +580,15 @@ export class Frontend {
|
|
|
460
580
|
return;
|
|
461
581
|
}
|
|
462
582
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
583
|
+
// Handle the command setpassword from Settings
|
|
463
584
|
if (command === 'setpassword') {
|
|
464
|
-
const password = param.slice(1, -1);
|
|
585
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
465
586
|
this.log.debug('setpassword', param, password);
|
|
466
587
|
await this.matterbridge.nodeContext?.set('password', password);
|
|
467
588
|
res.json({ message: 'Command received' });
|
|
468
589
|
return;
|
|
469
590
|
}
|
|
591
|
+
// Handle the command setbridgemode from Settings
|
|
470
592
|
if (command === 'setbridgemode') {
|
|
471
593
|
this.log.debug(`setbridgemode: ${param}`);
|
|
472
594
|
this.wssSendRestartRequired();
|
|
@@ -474,6 +596,7 @@ export class Frontend {
|
|
|
474
596
|
res.json({ message: 'Command received' });
|
|
475
597
|
return;
|
|
476
598
|
}
|
|
599
|
+
// Handle the command backup from Settings
|
|
477
600
|
if (command === 'backup') {
|
|
478
601
|
this.log.notice(`Prepairing the backup...`);
|
|
479
602
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridge.matterbridgeDirectory), path.join(this.matterbridge.matterbridgePluginDirectory));
|
|
@@ -482,31 +605,33 @@ export class Frontend {
|
|
|
482
605
|
res.json({ message: 'Command received' });
|
|
483
606
|
return;
|
|
484
607
|
}
|
|
608
|
+
// Handle the command setmbloglevel from Settings
|
|
485
609
|
if (command === 'setmbloglevel') {
|
|
486
610
|
this.log.debug('Matterbridge log level:', param);
|
|
487
611
|
if (param === 'Debug') {
|
|
488
|
-
this.log.logLevel = "debug"
|
|
612
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
489
613
|
}
|
|
490
614
|
else if (param === 'Info') {
|
|
491
|
-
this.log.logLevel = "info"
|
|
615
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
492
616
|
}
|
|
493
617
|
else if (param === 'Notice') {
|
|
494
|
-
this.log.logLevel = "notice"
|
|
618
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
495
619
|
}
|
|
496
620
|
else if (param === 'Warn') {
|
|
497
|
-
this.log.logLevel = "warn"
|
|
621
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
498
622
|
}
|
|
499
623
|
else if (param === 'Error') {
|
|
500
|
-
this.log.logLevel = "error"
|
|
624
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
501
625
|
}
|
|
502
626
|
else if (param === 'Fatal') {
|
|
503
|
-
this.log.logLevel = "fatal"
|
|
627
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
504
628
|
}
|
|
505
629
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
506
630
|
await this.matterbridge.setLogLevel(this.log.logLevel);
|
|
507
631
|
res.json({ message: 'Command received' });
|
|
508
632
|
return;
|
|
509
633
|
}
|
|
634
|
+
// Handle the command setmbloglevel from Settings
|
|
510
635
|
if (command === 'setmjloglevel') {
|
|
511
636
|
this.log.debug('Matter.js log level:', param);
|
|
512
637
|
if (param === 'Debug') {
|
|
@@ -531,30 +656,34 @@ export class Frontend {
|
|
|
531
656
|
res.json({ message: 'Command received' });
|
|
532
657
|
return;
|
|
533
658
|
}
|
|
659
|
+
// Handle the command setmdnsinterface from Settings
|
|
534
660
|
if (command === 'setmdnsinterface') {
|
|
535
|
-
param = param.slice(1, -1);
|
|
661
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
536
662
|
this.matterbridge.matterbridgeInformation.mattermdnsinterface = param;
|
|
537
663
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
538
664
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', param);
|
|
539
665
|
res.json({ message: 'Command received' });
|
|
540
666
|
return;
|
|
541
667
|
}
|
|
668
|
+
// Handle the command setipv4address from Settings
|
|
542
669
|
if (command === 'setipv4address') {
|
|
543
|
-
param = param.slice(1, -1);
|
|
670
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
544
671
|
this.matterbridge.matterbridgeInformation.matteripv4address = param;
|
|
545
672
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
546
673
|
await this.matterbridge.nodeContext?.set('matteripv4address', param);
|
|
547
674
|
res.json({ message: 'Command received' });
|
|
548
675
|
return;
|
|
549
676
|
}
|
|
677
|
+
// Handle the command setipv6address from Settings
|
|
550
678
|
if (command === 'setipv6address') {
|
|
551
|
-
param = param.slice(1, -1);
|
|
679
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
552
680
|
this.matterbridge.matterbridgeInformation.matteripv6address = param;
|
|
553
681
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
554
682
|
await this.matterbridge.nodeContext?.set('matteripv6address', param);
|
|
555
683
|
res.json({ message: 'Command received' });
|
|
556
684
|
return;
|
|
557
685
|
}
|
|
686
|
+
// Handle the command setmatterport from Settings
|
|
558
687
|
if (command === 'setmatterport') {
|
|
559
688
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
560
689
|
this.matterbridge.matterbridgeInformation.matterPort = port;
|
|
@@ -563,6 +692,7 @@ export class Frontend {
|
|
|
563
692
|
res.json({ message: 'Command received' });
|
|
564
693
|
return;
|
|
565
694
|
}
|
|
695
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
566
696
|
if (command === 'setmatterdiscriminator') {
|
|
567
697
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
568
698
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -571,6 +701,7 @@ export class Frontend {
|
|
|
571
701
|
res.json({ message: 'Command received' });
|
|
572
702
|
return;
|
|
573
703
|
}
|
|
704
|
+
// Handle the command setmatterpasscode from Settings
|
|
574
705
|
if (command === 'setmatterpasscode') {
|
|
575
706
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
576
707
|
this.matterbridge.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -579,17 +710,20 @@ export class Frontend {
|
|
|
579
710
|
res.json({ message: 'Command received' });
|
|
580
711
|
return;
|
|
581
712
|
}
|
|
713
|
+
// Handle the command setmbloglevel from Settings
|
|
582
714
|
if (command === 'setmblogfile') {
|
|
583
715
|
this.log.debug('Matterbridge file log:', param);
|
|
584
716
|
this.matterbridge.matterbridgeInformation.fileLogger = param === 'true';
|
|
585
717
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
718
|
+
// Create the file logger for matterbridge
|
|
586
719
|
if (param === 'true')
|
|
587
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug"
|
|
720
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
588
721
|
else
|
|
589
722
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
590
723
|
res.json({ message: 'Command received' });
|
|
591
724
|
return;
|
|
592
725
|
}
|
|
726
|
+
// Handle the command setmbloglevel from Settings
|
|
593
727
|
if (command === 'setmjlogfile') {
|
|
594
728
|
this.log.debug('Matter file log:', param);
|
|
595
729
|
this.matterbridge.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -616,40 +750,48 @@ export class Frontend {
|
|
|
616
750
|
res.json({ message: 'Command received' });
|
|
617
751
|
return;
|
|
618
752
|
}
|
|
753
|
+
// Handle the command unregister from Settings
|
|
619
754
|
if (command === 'unregister') {
|
|
620
755
|
await this.matterbridge.unregisterAndShutdownProcess();
|
|
621
756
|
res.json({ message: 'Command received' });
|
|
622
757
|
return;
|
|
623
758
|
}
|
|
759
|
+
// Handle the command reset from Settings
|
|
624
760
|
if (command === 'reset') {
|
|
625
761
|
await this.matterbridge.shutdownProcessAndReset();
|
|
626
762
|
res.json({ message: 'Command received' });
|
|
627
763
|
return;
|
|
628
764
|
}
|
|
765
|
+
// Handle the command factoryreset from Settings
|
|
629
766
|
if (command === 'factoryreset') {
|
|
630
767
|
await this.matterbridge.shutdownProcessAndFactoryReset();
|
|
631
768
|
res.json({ message: 'Command received' });
|
|
632
769
|
return;
|
|
633
770
|
}
|
|
771
|
+
// Handle the command shutdown from Header
|
|
634
772
|
if (command === 'shutdown') {
|
|
635
773
|
await this.matterbridge.shutdownProcess();
|
|
636
774
|
res.json({ message: 'Command received' });
|
|
637
775
|
return;
|
|
638
776
|
}
|
|
777
|
+
// Handle the command restart from Header
|
|
639
778
|
if (command === 'restart') {
|
|
640
779
|
await this.matterbridge.restartProcess();
|
|
641
780
|
res.json({ message: 'Command received' });
|
|
642
781
|
return;
|
|
643
782
|
}
|
|
783
|
+
// Handle the command update from Header
|
|
644
784
|
if (command === 'update') {
|
|
645
785
|
await this.matterbridge.updateProcess();
|
|
646
786
|
this.wssSendRestartRequired();
|
|
647
787
|
res.json({ message: 'Command received' });
|
|
648
788
|
return;
|
|
649
789
|
}
|
|
790
|
+
// Handle the command saveconfig from Home
|
|
650
791
|
if (command === 'saveconfig') {
|
|
651
792
|
param = param.replace(/\*/g, '\\');
|
|
652
793
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
794
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
653
795
|
if (!this.matterbridge.plugins.has(param)) {
|
|
654
796
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
655
797
|
}
|
|
@@ -664,6 +806,7 @@ export class Frontend {
|
|
|
664
806
|
res.json({ message: 'Command received' });
|
|
665
807
|
return;
|
|
666
808
|
}
|
|
809
|
+
// Handle the command installplugin from Home
|
|
667
810
|
if (command === 'installplugin') {
|
|
668
811
|
param = param.replace(/\*/g, '\\');
|
|
669
812
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
@@ -672,6 +815,7 @@ export class Frontend {
|
|
|
672
815
|
await this.matterbridge.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
673
816
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
674
817
|
this.wssSendSnackbarMessage(`Installed package ${param}`);
|
|
818
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
675
819
|
}
|
|
676
820
|
catch (error) {
|
|
677
821
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
@@ -680,16 +824,21 @@ export class Frontend {
|
|
|
680
824
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
681
825
|
this.wssSendRestartRequired();
|
|
682
826
|
param = param.split('@')[0];
|
|
827
|
+
// Also add the plugin to matterbridge so no return!
|
|
683
828
|
if (param === 'matterbridge') {
|
|
829
|
+
// If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
|
|
684
830
|
res.json({ message: 'Command received' });
|
|
685
831
|
return;
|
|
686
832
|
}
|
|
687
833
|
}
|
|
834
|
+
// Handle the command addplugin from Home
|
|
688
835
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
689
836
|
param = param.replace(/\*/g, '\\');
|
|
690
837
|
const plugin = await this.matterbridge.plugins.add(param);
|
|
691
838
|
if (plugin) {
|
|
692
839
|
if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
840
|
+
// We don't know now if the plugin is a dynamic platform or an accessory platform so we create the server node and the aggregator node
|
|
841
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
693
842
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
694
843
|
}
|
|
695
844
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been added', true).then(() => {
|
|
@@ -699,19 +848,21 @@ export class Frontend {
|
|
|
699
848
|
res.json({ message: 'Command received' });
|
|
700
849
|
return;
|
|
701
850
|
}
|
|
851
|
+
// Handle the command removeplugin from Home
|
|
702
852
|
if (command === 'removeplugin') {
|
|
703
853
|
if (!this.matterbridge.plugins.has(param)) {
|
|
704
854
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
705
855
|
}
|
|
706
856
|
else {
|
|
707
857
|
const plugin = this.matterbridge.plugins.get(param);
|
|
708
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
858
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true); // This will also close the server node in childbridge mode
|
|
709
859
|
await this.matterbridge.plugins.remove(param);
|
|
710
860
|
}
|
|
711
861
|
res.json({ message: 'Command received' });
|
|
712
862
|
this.wssSendRefreshRequired();
|
|
713
863
|
return;
|
|
714
864
|
}
|
|
865
|
+
// Handle the command enableplugin from Home
|
|
715
866
|
if (command === 'enableplugin') {
|
|
716
867
|
if (!this.matterbridge.plugins.has(param)) {
|
|
717
868
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -729,6 +880,7 @@ export class Frontend {
|
|
|
729
880
|
plugin.addedDevices = undefined;
|
|
730
881
|
await this.matterbridge.plugins.enable(param);
|
|
731
882
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') {
|
|
883
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
732
884
|
this.matterbridge.createDynamicPlugin(plugin, true);
|
|
733
885
|
}
|
|
734
886
|
this.matterbridge.plugins.load(plugin, true, 'The plugin has been enabled', true).then(() => {
|
|
@@ -740,6 +892,7 @@ export class Frontend {
|
|
|
740
892
|
this.wssSendRefreshRequired();
|
|
741
893
|
return;
|
|
742
894
|
}
|
|
895
|
+
// Handle the command disableplugin from Home
|
|
743
896
|
if (command === 'disableplugin') {
|
|
744
897
|
if (!this.matterbridge.plugins.has(param)) {
|
|
745
898
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -747,7 +900,7 @@ export class Frontend {
|
|
|
747
900
|
else {
|
|
748
901
|
const plugin = this.matterbridge.plugins.get(param);
|
|
749
902
|
if (plugin && plugin.enabled) {
|
|
750
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
903
|
+
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been disabled.', true); // This will also close the server node in childbridge mode
|
|
751
904
|
await this.matterbridge.plugins.disable(param);
|
|
752
905
|
}
|
|
753
906
|
}
|
|
@@ -756,6 +909,7 @@ export class Frontend {
|
|
|
756
909
|
return;
|
|
757
910
|
}
|
|
758
911
|
});
|
|
912
|
+
// Fallback for routing (must be the last route)
|
|
759
913
|
this.expressApp.get('*', (req, res) => {
|
|
760
914
|
this.log.debug('The frontend sent:', req.url);
|
|
761
915
|
this.log.debug('Response send file:', path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -764,24 +918,29 @@ export class Frontend {
|
|
|
764
918
|
this.log.debug(`Frontend initialized on port ${YELLOW}${this.port}${db} static ${UNDERLINE}${path.join(this.matterbridge.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
765
919
|
}
|
|
766
920
|
async stop() {
|
|
921
|
+
// Close the http server
|
|
767
922
|
if (this.httpServer) {
|
|
768
923
|
this.httpServer.close();
|
|
769
924
|
this.httpServer.removeAllListeners();
|
|
770
925
|
this.httpServer = undefined;
|
|
771
926
|
this.log.debug('Frontend http server closed successfully');
|
|
772
927
|
}
|
|
928
|
+
// Close the https server
|
|
773
929
|
if (this.httpsServer) {
|
|
774
930
|
this.httpsServer.close();
|
|
775
931
|
this.httpsServer.removeAllListeners();
|
|
776
932
|
this.httpsServer = undefined;
|
|
777
933
|
this.log.debug('Frontend https server closed successfully');
|
|
778
934
|
}
|
|
935
|
+
// Remove listeners from the express app
|
|
779
936
|
if (this.expressApp) {
|
|
780
937
|
this.expressApp.removeAllListeners();
|
|
781
938
|
this.expressApp = undefined;
|
|
782
939
|
this.log.debug('Frontend app closed successfully');
|
|
783
940
|
}
|
|
941
|
+
// Close the WebSocket server
|
|
784
942
|
if (this.webSocketServer) {
|
|
943
|
+
// Close all active connections
|
|
785
944
|
this.webSocketServer.clients.forEach((client) => {
|
|
786
945
|
if (client.readyState === WebSocket.OPEN) {
|
|
787
946
|
client.close();
|
|
@@ -798,6 +957,7 @@ export class Frontend {
|
|
|
798
957
|
this.webSocketServer = undefined;
|
|
799
958
|
}
|
|
800
959
|
}
|
|
960
|
+
// Function to format bytes to KB, MB, or GB
|
|
801
961
|
formatMemoryUsage = (bytes) => {
|
|
802
962
|
if (bytes >= 1024 ** 3) {
|
|
803
963
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -809,6 +969,7 @@ export class Frontend {
|
|
|
809
969
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
810
970
|
}
|
|
811
971
|
};
|
|
972
|
+
// Function to format system uptime with only the most significant unit
|
|
812
973
|
formatOsUpTime = (seconds) => {
|
|
813
974
|
if (seconds >= 86400) {
|
|
814
975
|
const days = Math.floor(seconds / 86400);
|
|
@@ -824,8 +985,13 @@ export class Frontend {
|
|
|
824
985
|
}
|
|
825
986
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
826
987
|
};
|
|
988
|
+
/**
|
|
989
|
+
* Retrieves the api settings data.
|
|
990
|
+
* @returns {Promise<object>} A promise that resolve in the api settings object.
|
|
991
|
+
*/
|
|
827
992
|
async getApiSettings() {
|
|
828
993
|
const { lastCpuUsage } = await import('./cli.js');
|
|
994
|
+
// Update the system information
|
|
829
995
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
830
996
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
831
997
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -834,6 +1000,7 @@ export class Frontend {
|
|
|
834
1000
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
835
1001
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
836
1002
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1003
|
+
// Update the matterbridge information
|
|
837
1004
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
838
1005
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
839
1006
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -852,6 +1019,11 @@ export class Frontend {
|
|
|
852
1019
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
853
1020
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
854
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Retrieves the cluster text description from a given device.
|
|
1024
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
1025
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
1026
|
+
*/
|
|
855
1027
|
getClusterTextFromDevice(device) {
|
|
856
1028
|
const getAttribute = (device, cluster, attribute) => {
|
|
857
1029
|
let value = undefined;
|
|
@@ -890,6 +1062,7 @@ export class Frontend {
|
|
|
890
1062
|
};
|
|
891
1063
|
let attributes = '';
|
|
892
1064
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1065
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
893
1066
|
if (typeof attributeValue === 'undefined')
|
|
894
1067
|
return;
|
|
895
1068
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -967,8 +1140,13 @@ export class Frontend {
|
|
|
967
1140
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
968
1141
|
attributes += `${getUserLabel(device)} `;
|
|
969
1142
|
});
|
|
1143
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
970
1144
|
return attributes.trimStart().trimEnd();
|
|
971
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1148
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1149
|
+
*/
|
|
972
1150
|
getBaseRegisteredPlugins() {
|
|
973
1151
|
const baseRegisteredPlugins = [];
|
|
974
1152
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -999,6 +1177,13 @@ export class Frontend {
|
|
|
999
1177
|
}
|
|
1000
1178
|
return baseRegisteredPlugins;
|
|
1001
1179
|
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1182
|
+
*
|
|
1183
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1184
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1185
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1186
|
+
*/
|
|
1002
1187
|
async wsMessageHandler(client, message) {
|
|
1003
1188
|
let data;
|
|
1004
1189
|
try {
|
|
@@ -1139,8 +1324,10 @@ export class Frontend {
|
|
|
1139
1324
|
else if (data.method === '/api/devices') {
|
|
1140
1325
|
const devices = [];
|
|
1141
1326
|
this.matterbridge.devices.forEach(async (device) => {
|
|
1327
|
+
// Filter by pluginName if provided
|
|
1142
1328
|
if (data.params.pluginName && data.params.pluginName !== device.plugin)
|
|
1143
1329
|
return;
|
|
1330
|
+
// Check if the device has the required properties
|
|
1144
1331
|
if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1145
1332
|
return;
|
|
1146
1333
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -1223,6 +1410,7 @@ export class Frontend {
|
|
|
1223
1410
|
});
|
|
1224
1411
|
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1225
1412
|
deviceTypes = [];
|
|
1413
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1226
1414
|
const name = childEndpoint.endpoint?.id;
|
|
1227
1415
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1228
1416
|
clusterServers.forEach((clusterServer) => {
|
|
@@ -1305,85 +1493,139 @@ export class Frontend {
|
|
|
1305
1493
|
return;
|
|
1306
1494
|
}
|
|
1307
1495
|
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Sends a WebSocket message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1498
|
+
*
|
|
1499
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1500
|
+
* @param {string} time - The time string of the message
|
|
1501
|
+
* @param {string} name - The logger name of the message
|
|
1502
|
+
* @param {string} message - The content of the message.
|
|
1503
|
+
*/
|
|
1308
1504
|
wssSendMessage(level, time, name, message) {
|
|
1309
1505
|
if (!level || !time || !name || !message)
|
|
1310
1506
|
return;
|
|
1507
|
+
// Remove ANSI escape codes from the message
|
|
1508
|
+
// eslint-disable-next-line no-control-regex
|
|
1311
1509
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1510
|
+
// Remove leading asterisks from the message
|
|
1312
1511
|
message = message.replace(/^\*+/, '');
|
|
1512
|
+
// Replace all occurrences of \t and \n
|
|
1313
1513
|
message = message.replace(/[\t\n]/g, '');
|
|
1514
|
+
// Remove non-printable characters
|
|
1515
|
+
// eslint-disable-next-line no-control-regex
|
|
1314
1516
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1517
|
+
// Replace all occurrences of \" with "
|
|
1315
1518
|
message = message.replace(/\\"/g, '"');
|
|
1519
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1316
1520
|
const maxContinuousLength = 100;
|
|
1317
1521
|
const keepStartLength = 20;
|
|
1318
1522
|
const keepEndLength = 20;
|
|
1523
|
+
// Split the message into words
|
|
1319
1524
|
message = message
|
|
1320
1525
|
.split(' ')
|
|
1321
1526
|
.map((word) => {
|
|
1527
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1322
1528
|
if (word.length > maxContinuousLength) {
|
|
1323
1529
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1324
1530
|
}
|
|
1325
1531
|
return word;
|
|
1326
1532
|
})
|
|
1327
1533
|
.join(' ');
|
|
1534
|
+
// Send the message to all connected clients
|
|
1328
1535
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1329
1536
|
if (client.readyState === WebSocket.OPEN) {
|
|
1330
1537
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1331
1538
|
}
|
|
1332
1539
|
});
|
|
1333
1540
|
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1543
|
+
*
|
|
1544
|
+
*/
|
|
1334
1545
|
wssSendRefreshRequired() {
|
|
1335
1546
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1336
1547
|
this.matterbridge.matterbridgeInformation.refreshRequired = true;
|
|
1548
|
+
// Send the message to all connected clients
|
|
1337
1549
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1338
1550
|
if (client.readyState === WebSocket.OPEN) {
|
|
1339
1551
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
1340
1552
|
}
|
|
1341
1553
|
});
|
|
1342
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1557
|
+
*
|
|
1558
|
+
*/
|
|
1343
1559
|
wssSendRestartRequired() {
|
|
1344
1560
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1345
1561
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1346
1562
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1563
|
+
// Send the message to all connected clients
|
|
1347
1564
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1348
1565
|
if (client.readyState === WebSocket.OPEN) {
|
|
1349
1566
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1350
1567
|
}
|
|
1351
1568
|
});
|
|
1352
1569
|
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Sends a memory update message to all connected clients.
|
|
1572
|
+
*
|
|
1573
|
+
*/
|
|
1353
1574
|
wssSendCpuUpdate(cpuUsage) {
|
|
1354
1575
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1576
|
+
// Send the message to all connected clients
|
|
1355
1577
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1356
1578
|
if (client.readyState === WebSocket.OPEN) {
|
|
1357
1579
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1358
1580
|
}
|
|
1359
1581
|
});
|
|
1360
1582
|
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Sends a cpu update message to all connected clients.
|
|
1585
|
+
*
|
|
1586
|
+
*/
|
|
1361
1587
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1362
1588
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1589
|
+
// Send the message to all connected clients
|
|
1363
1590
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1364
1591
|
if (client.readyState === WebSocket.OPEN) {
|
|
1365
1592
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1366
1593
|
}
|
|
1367
1594
|
});
|
|
1368
1595
|
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Sends a memory update message to all connected clients.
|
|
1598
|
+
*
|
|
1599
|
+
*/
|
|
1369
1600
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1370
1601
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1602
|
+
// Send the message to all connected clients
|
|
1371
1603
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1372
1604
|
if (client.readyState === WebSocket.OPEN) {
|
|
1373
1605
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1374
1606
|
}
|
|
1375
1607
|
});
|
|
1376
1608
|
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Sends a cpu update message to all connected clients.
|
|
1611
|
+
*
|
|
1612
|
+
*/
|
|
1377
1613
|
wssSendSnackbarMessage(message, timeout = 5) {
|
|
1378
1614
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
1615
|
+
// Send the message to all connected clients
|
|
1379
1616
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1380
1617
|
if (client.readyState === WebSocket.OPEN) {
|
|
1381
1618
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { message, timeout } }));
|
|
1382
1619
|
}
|
|
1383
1620
|
});
|
|
1384
1621
|
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Sends a message to all connected clients.
|
|
1624
|
+
*
|
|
1625
|
+
*/
|
|
1385
1626
|
wssBroadcastMessage(id, method, params) {
|
|
1386
1627
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
1628
|
+
// Send the message to all connected clients
|
|
1387
1629
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1388
1630
|
if (client.readyState === WebSocket.OPEN) {
|
|
1389
1631
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1391,3 +1633,4 @@ export class Frontend {
|
|
|
1391
1633
|
});
|
|
1392
1634
|
}
|
|
1393
1635
|
}
|
|
1636
|
+
//# sourceMappingURL=frontend.js.map
|