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