matterbridge 3.1.1-dev-20250704-aff5fcb → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -3
- package/README.md +5 -25
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +93 -6
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +36 -0
- package/dist/cliEmitter.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 +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -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/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +9 -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/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +87 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +83 -6
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +242 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +91 -7
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +103 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +82 -6
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/frontend.d.ts +302 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +439 -20
- package/dist/frontend.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +59 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +47 -0
- package/dist/globalMatterbridge.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -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 +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +450 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +802 -50
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1340 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +61 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +709 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +579 -15
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +36 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1179 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1027 -42
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +322 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +310 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +233 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +192 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +291 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +269 -5
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -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 +59 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +54 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +117 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +263 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -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 +49 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +58 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +76 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +83 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +11 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +18 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +56 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +62 -9
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,29 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Frontend.
|
|
3
|
+
*
|
|
4
|
+
* @file frontend.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2025-01-13
|
|
7
|
+
* @version 1.1.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node modules
|
|
1
25
|
import { createServer } from 'node:http';
|
|
2
26
|
import https from 'node:https';
|
|
3
27
|
import os from 'node:os';
|
|
4
28
|
import path from 'node:path';
|
|
5
29
|
import { promises as fs } from 'node:fs';
|
|
6
30
|
import EventEmitter from 'node:events';
|
|
31
|
+
// Third-party modules
|
|
7
32
|
import express from 'express';
|
|
8
33
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
34
|
import multer from 'multer';
|
|
35
|
+
// AnsiLogger module
|
|
10
36
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from 'node-ansi-logger';
|
|
37
|
+
// @matter
|
|
11
38
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
12
39
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
|
|
14
42
|
import { plg } from './matterbridgeTypes.js';
|
|
15
43
|
import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
|
|
44
|
+
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
45
|
+
/**
|
|
46
|
+
* Websocket message ID for logging.
|
|
47
|
+
*
|
|
48
|
+
* @constant {number}
|
|
49
|
+
*/
|
|
16
50
|
export const WS_ID_LOG = 0;
|
|
51
|
+
/**
|
|
52
|
+
* Websocket message ID indicating a refresh is needed.
|
|
53
|
+
*
|
|
54
|
+
* @constant {number}
|
|
55
|
+
*/
|
|
17
56
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Websocket message ID indicating a restart is needed.
|
|
59
|
+
*
|
|
60
|
+
* @constant {number}
|
|
61
|
+
*/
|
|
18
62
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
63
|
+
/**
|
|
64
|
+
* Websocket message ID indicating a cpu update.
|
|
65
|
+
*
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
19
68
|
export const WS_ID_CPU_UPDATE = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
*
|
|
72
|
+
* @constant {number}
|
|
73
|
+
*/
|
|
20
74
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
75
|
+
/**
|
|
76
|
+
* Websocket message ID indicating an uptime update.
|
|
77
|
+
*
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
21
80
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
81
|
+
/**
|
|
82
|
+
* Websocket message ID indicating a snackbar message.
|
|
83
|
+
*
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
22
86
|
export const WS_ID_SNACKBAR = 6;
|
|
87
|
+
/**
|
|
88
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
89
|
+
*
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
23
92
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
93
|
+
/**
|
|
94
|
+
* Websocket message ID indicating a state update.
|
|
95
|
+
*
|
|
96
|
+
* @constant {number}
|
|
97
|
+
*/
|
|
24
98
|
export const WS_ID_STATEUPDATE = 8;
|
|
99
|
+
/**
|
|
100
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
101
|
+
*
|
|
102
|
+
* @constant {number}
|
|
103
|
+
*/
|
|
25
104
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
105
|
+
/**
|
|
106
|
+
* Websocket message ID indicating a shelly system update.
|
|
107
|
+
* check:
|
|
108
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
109
|
+
* perform:
|
|
110
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
111
|
+
*
|
|
112
|
+
* @constant {number}
|
|
113
|
+
*/
|
|
26
114
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
115
|
+
/**
|
|
116
|
+
* Websocket message ID indicating a shelly main update.
|
|
117
|
+
* check:
|
|
118
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
119
|
+
* perform:
|
|
120
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
121
|
+
*
|
|
122
|
+
* @constant {number}
|
|
123
|
+
*/
|
|
27
124
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
28
125
|
export class Frontend extends EventEmitter {
|
|
29
126
|
matterbridge;
|
|
@@ -37,7 +134,7 @@ export class Frontend extends EventEmitter {
|
|
|
37
134
|
constructor(matterbridge) {
|
|
38
135
|
super();
|
|
39
136
|
this.matterbridge = matterbridge;
|
|
40
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
137
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
41
138
|
}
|
|
42
139
|
set logLevel(logLevel) {
|
|
43
140
|
this.log.logLevel = logLevel;
|
|
@@ -45,13 +142,50 @@ export class Frontend extends EventEmitter {
|
|
|
45
142
|
async start(port = 8283) {
|
|
46
143
|
this.port = port;
|
|
47
144
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
145
|
+
// Initialize multer with the upload directory
|
|
48
146
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
49
147
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
50
148
|
const upload = multer({ dest: uploadDir });
|
|
149
|
+
// Create the express app that serves the frontend
|
|
51
150
|
this.expressApp = express();
|
|
151
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
152
|
+
/*
|
|
153
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
154
|
+
for (const method of methods) {
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
159
|
+
try {
|
|
160
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
161
|
+
return original(path, ...rest);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
*/
|
|
169
|
+
// Log all requests to the server for debugging
|
|
170
|
+
/*
|
|
171
|
+
this.expressApp.use((req, res, next) => {
|
|
172
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
173
|
+
next();
|
|
174
|
+
});
|
|
175
|
+
*/
|
|
176
|
+
// Serve static files from '/static' endpoint
|
|
52
177
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
53
178
|
if (!hasParameter('ssl')) {
|
|
54
|
-
|
|
179
|
+
// Create an HTTP server and attach the express app
|
|
180
|
+
try {
|
|
181
|
+
this.httpServer = createServer(this.expressApp);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
this.log.error(`Failed to create HTTP server: ${error}`);
|
|
185
|
+
this.emit('server_error', error);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// Listen on the specified port
|
|
55
189
|
if (hasParameter('ingress')) {
|
|
56
190
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
57
191
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -83,6 +217,7 @@ export class Frontend extends EventEmitter {
|
|
|
83
217
|
});
|
|
84
218
|
}
|
|
85
219
|
else {
|
|
220
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
86
221
|
let cert;
|
|
87
222
|
try {
|
|
88
223
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -90,6 +225,7 @@ export class Frontend extends EventEmitter {
|
|
|
90
225
|
}
|
|
91
226
|
catch (error) {
|
|
92
227
|
this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
|
|
228
|
+
this.emit('server_error', error);
|
|
93
229
|
return;
|
|
94
230
|
}
|
|
95
231
|
let key;
|
|
@@ -99,6 +235,7 @@ export class Frontend extends EventEmitter {
|
|
|
99
235
|
}
|
|
100
236
|
catch (error) {
|
|
101
237
|
this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
|
|
238
|
+
this.emit('server_error', error);
|
|
102
239
|
return;
|
|
103
240
|
}
|
|
104
241
|
let ca;
|
|
@@ -110,7 +247,16 @@ export class Frontend extends EventEmitter {
|
|
|
110
247
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
111
248
|
}
|
|
112
249
|
const serverOptions = { cert, key, ca };
|
|
113
|
-
|
|
250
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
251
|
+
try {
|
|
252
|
+
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
this.log.error(`Failed to create HTTPS server: ${error}`);
|
|
256
|
+
this.emit('server_error', error);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Listen on the specified port
|
|
114
260
|
if (hasParameter('ingress')) {
|
|
115
261
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
116
262
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -143,16 +289,18 @@ export class Frontend extends EventEmitter {
|
|
|
143
289
|
}
|
|
144
290
|
if (this.initializeError)
|
|
145
291
|
return;
|
|
292
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
146
293
|
const wssPort = this.port;
|
|
147
294
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
148
295
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
149
296
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
150
297
|
const clientIp = request.socket.remoteAddress;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
298
|
+
// Set the global logger callback for the WebSocketServer
|
|
299
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
300
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
301
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
302
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
303
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
156
304
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
157
305
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
158
306
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -187,7 +335,7 @@ export class Frontend extends EventEmitter {
|
|
|
187
335
|
this.webSocketServer.on('error', (ws, error) => {
|
|
188
336
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
189
337
|
});
|
|
190
|
-
|
|
338
|
+
// Subscribe to cli events
|
|
191
339
|
cliEmitter.removeAllListeners();
|
|
192
340
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
193
341
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -198,6 +346,8 @@ export class Frontend extends EventEmitter {
|
|
|
198
346
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
199
347
|
this.wssSendCpuUpdate(cpuUsage);
|
|
200
348
|
});
|
|
349
|
+
// Endpoint to validate login code
|
|
350
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
201
351
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
202
352
|
const { password } = req.body;
|
|
203
353
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -216,23 +366,27 @@ export class Frontend extends EventEmitter {
|
|
|
216
366
|
this.log.warn('/api/login error wrong password');
|
|
217
367
|
res.json({ valid: false });
|
|
218
368
|
}
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
219
370
|
}
|
|
220
371
|
catch (error) {
|
|
221
372
|
this.log.error('/api/login error getting password');
|
|
222
373
|
res.json({ valid: false });
|
|
223
374
|
}
|
|
224
375
|
});
|
|
376
|
+
// Endpoint to provide health check for docker
|
|
225
377
|
this.expressApp.get('/health', (req, res) => {
|
|
226
378
|
this.log.debug('Express received /health');
|
|
227
379
|
const healthStatus = {
|
|
228
|
-
status: 'ok',
|
|
229
|
-
uptime: process.uptime(),
|
|
230
|
-
timestamp: new Date().toISOString(),
|
|
380
|
+
status: 'ok', // Indicate service is healthy
|
|
381
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
382
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
231
383
|
};
|
|
232
384
|
res.status(200).json(healthStatus);
|
|
233
385
|
});
|
|
386
|
+
// Endpoint to provide memory usage details
|
|
234
387
|
this.expressApp.get('/memory', async (req, res) => {
|
|
235
388
|
this.log.debug('Express received /memory');
|
|
389
|
+
// Memory usage from process
|
|
236
390
|
const memoryUsageRaw = process.memoryUsage();
|
|
237
391
|
const memoryUsage = {
|
|
238
392
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -241,10 +395,13 @@ export class Frontend extends EventEmitter {
|
|
|
241
395
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
242
396
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
243
397
|
};
|
|
398
|
+
// V8 heap statistics
|
|
244
399
|
const { default: v8 } = await import('node:v8');
|
|
245
400
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
246
401
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
402
|
+
// Format heapStats
|
|
247
403
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
404
|
+
// Format heapSpaces
|
|
248
405
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
249
406
|
...space,
|
|
250
407
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -262,19 +419,23 @@ export class Frontend extends EventEmitter {
|
|
|
262
419
|
};
|
|
263
420
|
res.status(200).json(memoryReport);
|
|
264
421
|
});
|
|
422
|
+
// Endpoint to provide settings
|
|
265
423
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
266
424
|
this.log.debug('The frontend sent /api/settings');
|
|
267
425
|
res.json(await this.getApiSettings());
|
|
268
426
|
});
|
|
427
|
+
// Endpoint to provide plugins
|
|
269
428
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
270
429
|
this.log.debug('The frontend sent /api/plugins');
|
|
271
430
|
res.json(this.getBaseRegisteredPlugins());
|
|
272
431
|
});
|
|
432
|
+
// Endpoint to provide devices
|
|
273
433
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
274
434
|
this.log.debug('The frontend sent /api/devices');
|
|
275
435
|
const devices = await this.getDevices();
|
|
276
436
|
res.json(devices);
|
|
277
437
|
});
|
|
438
|
+
// Endpoint to view the matterbridge log
|
|
278
439
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
279
440
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
280
441
|
try {
|
|
@@ -287,6 +448,7 @@ export class Frontend extends EventEmitter {
|
|
|
287
448
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
288
449
|
}
|
|
289
450
|
});
|
|
451
|
+
// Endpoint to view the matter.js log
|
|
290
452
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
291
453
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
292
454
|
try {
|
|
@@ -299,6 +461,7 @@ export class Frontend extends EventEmitter {
|
|
|
299
461
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
300
462
|
}
|
|
301
463
|
});
|
|
464
|
+
// Endpoint to view the shelly log
|
|
302
465
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
303
466
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
304
467
|
try {
|
|
@@ -311,9 +474,11 @@ export class Frontend extends EventEmitter {
|
|
|
311
474
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
312
475
|
}
|
|
313
476
|
});
|
|
477
|
+
// Endpoint to download the matterbridge log
|
|
314
478
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
315
479
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
316
480
|
try {
|
|
481
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
317
482
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
318
483
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
|
|
319
484
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
|
|
@@ -323,16 +488,20 @@ export class Frontend extends EventEmitter {
|
|
|
323
488
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
324
489
|
}
|
|
325
490
|
res.type('text/plain');
|
|
491
|
+
// res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
326
492
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
493
|
+
/* istanbul ignore if */
|
|
327
494
|
if (error) {
|
|
328
495
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
329
496
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
330
497
|
}
|
|
331
498
|
});
|
|
332
499
|
});
|
|
500
|
+
// Endpoint to download the matter log
|
|
333
501
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
334
502
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
335
503
|
try {
|
|
504
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
336
505
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
337
506
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
338
507
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -343,15 +512,18 @@ export class Frontend extends EventEmitter {
|
|
|
343
512
|
}
|
|
344
513
|
res.type('text/plain');
|
|
345
514
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
515
|
+
/* istanbul ignore if */
|
|
346
516
|
if (error) {
|
|
347
517
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
348
518
|
res.status(500).send('Error downloading the matter log file');
|
|
349
519
|
}
|
|
350
520
|
});
|
|
351
521
|
});
|
|
522
|
+
// Endpoint to download the shelly log
|
|
352
523
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
353
524
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
354
525
|
try {
|
|
526
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
355
527
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
356
528
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
357
529
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -362,12 +534,14 @@ export class Frontend extends EventEmitter {
|
|
|
362
534
|
}
|
|
363
535
|
res.type('text/plain');
|
|
364
536
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
537
|
+
/* istanbul ignore if */
|
|
365
538
|
if (error) {
|
|
366
539
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
367
540
|
res.status(500).send('Error downloading Shelly system log file');
|
|
368
541
|
}
|
|
369
542
|
});
|
|
370
543
|
});
|
|
544
|
+
// Endpoint to download the matterbridge storage directory
|
|
371
545
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
372
546
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
373
547
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -378,6 +552,7 @@ export class Frontend extends EventEmitter {
|
|
|
378
552
|
}
|
|
379
553
|
});
|
|
380
554
|
});
|
|
555
|
+
// Endpoint to download the matter storage file
|
|
381
556
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
382
557
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
383
558
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -388,6 +563,7 @@ export class Frontend extends EventEmitter {
|
|
|
388
563
|
}
|
|
389
564
|
});
|
|
390
565
|
});
|
|
566
|
+
// Endpoint to download the matterbridge plugin directory
|
|
391
567
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
392
568
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
393
569
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -398,6 +574,7 @@ export class Frontend extends EventEmitter {
|
|
|
398
574
|
}
|
|
399
575
|
});
|
|
400
576
|
});
|
|
577
|
+
// Endpoint to download the matterbridge plugin config files
|
|
401
578
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
402
579
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
403
580
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
@@ -408,6 +585,7 @@ export class Frontend extends EventEmitter {
|
|
|
408
585
|
}
|
|
409
586
|
});
|
|
410
587
|
});
|
|
588
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
411
589
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
412
590
|
this.log.debug('The frontend sent /api/download-backup');
|
|
413
591
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -417,6 +595,7 @@ export class Frontend extends EventEmitter {
|
|
|
417
595
|
}
|
|
418
596
|
});
|
|
419
597
|
});
|
|
598
|
+
// Endpoint to upload a package
|
|
420
599
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
421
600
|
const { filename } = req.body;
|
|
422
601
|
const file = req.file;
|
|
@@ -426,10 +605,13 @@ export class Frontend extends EventEmitter {
|
|
|
426
605
|
return;
|
|
427
606
|
}
|
|
428
607
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
608
|
+
// Define the path where the plugin file will be saved
|
|
429
609
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
430
610
|
try {
|
|
611
|
+
// Move the uploaded file to the specified path
|
|
431
612
|
await fs.rename(file.path, filePath);
|
|
432
613
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
614
|
+
// Install the plugin package
|
|
433
615
|
if (filename.endsWith('.tgz')) {
|
|
434
616
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
435
617
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -449,6 +631,7 @@ export class Frontend extends EventEmitter {
|
|
|
449
631
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
450
632
|
}
|
|
451
633
|
});
|
|
634
|
+
// Fallback for routing (must be the last route)
|
|
452
635
|
this.expressApp.use((req, res) => {
|
|
453
636
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
454
637
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -457,12 +640,15 @@ export class Frontend extends EventEmitter {
|
|
|
457
640
|
}
|
|
458
641
|
async stop() {
|
|
459
642
|
this.log.debug('Stopping the frontend...');
|
|
643
|
+
// Remove listeners from the express app
|
|
460
644
|
if (this.expressApp) {
|
|
461
645
|
this.expressApp.removeAllListeners();
|
|
462
646
|
this.expressApp = undefined;
|
|
463
647
|
this.log.debug('Frontend app closed successfully');
|
|
464
648
|
}
|
|
649
|
+
// Close the WebSocket server
|
|
465
650
|
if (this.webSocketServer) {
|
|
651
|
+
// Close all active connections
|
|
466
652
|
this.webSocketServer.clients.forEach((client) => {
|
|
467
653
|
if (client.readyState === WebSocket.OPEN) {
|
|
468
654
|
client.close();
|
|
@@ -482,6 +668,7 @@ export class Frontend extends EventEmitter {
|
|
|
482
668
|
this.webSocketServer.removeAllListeners();
|
|
483
669
|
this.webSocketServer = undefined;
|
|
484
670
|
}
|
|
671
|
+
// Close the http server
|
|
485
672
|
if (this.httpServer) {
|
|
486
673
|
await withTimeout(new Promise((resolve) => {
|
|
487
674
|
this.httpServer?.close((error) => {
|
|
@@ -498,6 +685,7 @@ export class Frontend extends EventEmitter {
|
|
|
498
685
|
this.httpServer = undefined;
|
|
499
686
|
this.log.debug('Frontend http server closed successfully');
|
|
500
687
|
}
|
|
688
|
+
// Close the https server
|
|
501
689
|
if (this.httpsServer) {
|
|
502
690
|
await withTimeout(new Promise((resolve) => {
|
|
503
691
|
this.httpsServer?.close((error) => {
|
|
@@ -516,6 +704,7 @@ export class Frontend extends EventEmitter {
|
|
|
516
704
|
}
|
|
517
705
|
this.log.debug('Frontend stopped successfully');
|
|
518
706
|
}
|
|
707
|
+
// Function to format bytes to KB, MB, or GB
|
|
519
708
|
formatMemoryUsage = (bytes) => {
|
|
520
709
|
if (bytes >= 1024 ** 3) {
|
|
521
710
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -527,6 +716,7 @@ export class Frontend extends EventEmitter {
|
|
|
527
716
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
528
717
|
}
|
|
529
718
|
};
|
|
719
|
+
// Function to format system uptime with only the most significant unit
|
|
530
720
|
formatOsUpTime = (seconds) => {
|
|
531
721
|
if (seconds >= 86400) {
|
|
532
722
|
const days = Math.floor(seconds / 86400);
|
|
@@ -542,8 +732,13 @@ export class Frontend extends EventEmitter {
|
|
|
542
732
|
}
|
|
543
733
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
544
734
|
};
|
|
735
|
+
/**
|
|
736
|
+
* Retrieves the api settings data.
|
|
737
|
+
*
|
|
738
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
739
|
+
*/
|
|
545
740
|
async getApiSettings() {
|
|
546
|
-
|
|
741
|
+
// Update the system information
|
|
547
742
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
548
743
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
549
744
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -552,6 +747,7 @@ export class Frontend extends EventEmitter {
|
|
|
552
747
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
553
748
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
554
749
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
750
|
+
// Update the matterbridge information
|
|
555
751
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
556
752
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
557
753
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -570,6 +766,12 @@ export class Frontend extends EventEmitter {
|
|
|
570
766
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
571
767
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
572
768
|
}
|
|
769
|
+
/**
|
|
770
|
+
* Retrieves the reachable attribute.
|
|
771
|
+
*
|
|
772
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
773
|
+
* @returns {boolean} The reachable attribute.
|
|
774
|
+
*/
|
|
573
775
|
getReachability(device) {
|
|
574
776
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
575
777
|
return false;
|
|
@@ -581,6 +783,12 @@ export class Frontend extends EventEmitter {
|
|
|
581
783
|
return true;
|
|
582
784
|
return false;
|
|
583
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Retrieves the power source attribute.
|
|
788
|
+
*
|
|
789
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
|
|
790
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
791
|
+
*/
|
|
584
792
|
getPowerSource(endpoint) {
|
|
585
793
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
586
794
|
return undefined;
|
|
@@ -596,13 +804,21 @@ export class Frontend extends EventEmitter {
|
|
|
596
804
|
}
|
|
597
805
|
return;
|
|
598
806
|
};
|
|
807
|
+
// Root endpoint
|
|
599
808
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
600
809
|
return powerSource(endpoint);
|
|
810
|
+
// Child endpoints
|
|
601
811
|
for (const child of endpoint.getChildEndpoints()) {
|
|
602
812
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
603
813
|
return powerSource(child);
|
|
604
814
|
}
|
|
605
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Retrieves the matter pairing code from a given device.
|
|
818
|
+
*
|
|
819
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object to retrieve the QR pairing code from.
|
|
820
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
821
|
+
*/
|
|
606
822
|
getMatterDataFromDevice(device) {
|
|
607
823
|
if (device.mode === 'server' && device.serverNode && device.serverContext) {
|
|
608
824
|
return {
|
|
@@ -614,6 +830,12 @@ export class Frontend extends EventEmitter {
|
|
|
614
830
|
};
|
|
615
831
|
}
|
|
616
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Retrieves the cluster text description from a given device.
|
|
835
|
+
*
|
|
836
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
837
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
838
|
+
*/
|
|
617
839
|
getClusterTextFromDevice(device) {
|
|
618
840
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
619
841
|
return '';
|
|
@@ -654,7 +876,19 @@ export class Frontend extends EventEmitter {
|
|
|
654
876
|
};
|
|
655
877
|
let attributes = '';
|
|
656
878
|
let supportedModes = [];
|
|
879
|
+
/*
|
|
880
|
+
Object.keys(device.behaviors.supported).forEach((clusterName) => {
|
|
881
|
+
const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
|
|
882
|
+
// console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
|
|
883
|
+
if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
|
|
884
|
+
Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
|
|
885
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
*/
|
|
657
890
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
891
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
658
892
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
659
893
|
return;
|
|
660
894
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -746,8 +980,14 @@ export class Frontend extends EventEmitter {
|
|
|
746
980
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
747
981
|
attributes += `${getUserLabel(device)} `;
|
|
748
982
|
});
|
|
983
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
749
984
|
return attributes.trimStart().trimEnd();
|
|
750
985
|
}
|
|
986
|
+
/**
|
|
987
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
988
|
+
*
|
|
989
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
990
|
+
*/
|
|
751
991
|
getBaseRegisteredPlugins() {
|
|
752
992
|
const baseRegisteredPlugins = [];
|
|
753
993
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -786,11 +1026,19 @@ export class Frontend extends EventEmitter {
|
|
|
786
1026
|
}
|
|
787
1027
|
return baseRegisteredPlugins;
|
|
788
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Retrieves the devices from Matterbridge.
|
|
1031
|
+
*
|
|
1032
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1033
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1034
|
+
*/
|
|
789
1035
|
async getDevices(pluginName) {
|
|
790
1036
|
const devices = [];
|
|
791
1037
|
for (const device of this.matterbridge.devices.array()) {
|
|
1038
|
+
// Filter by pluginName if provided
|
|
792
1039
|
if (pluginName && pluginName !== device.plugin)
|
|
793
1040
|
continue;
|
|
1041
|
+
// Check if the device has the required properties
|
|
794
1042
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
795
1043
|
continue;
|
|
796
1044
|
devices.push({
|
|
@@ -810,22 +1058,37 @@ export class Frontend extends EventEmitter {
|
|
|
810
1058
|
}
|
|
811
1059
|
return devices;
|
|
812
1060
|
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1063
|
+
*
|
|
1064
|
+
* Response for /api/clusters
|
|
1065
|
+
*
|
|
1066
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1067
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1068
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1069
|
+
*/
|
|
813
1070
|
getClusters(pluginName, endpointNumber) {
|
|
814
1071
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
815
1072
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
816
1073
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
817
1074
|
return;
|
|
818
1075
|
}
|
|
1076
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1077
|
+
// Get the device types from the main endpoint
|
|
819
1078
|
const deviceTypes = [];
|
|
820
1079
|
const clusters = [];
|
|
821
1080
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
822
1081
|
deviceTypes.push(d.deviceType);
|
|
823
1082
|
});
|
|
1083
|
+
// Get the clusters from the main endpoint
|
|
824
1084
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
825
1085
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
826
1086
|
return;
|
|
827
1087
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
828
1088
|
return;
|
|
1089
|
+
// console.log(
|
|
1090
|
+
// `${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}`,
|
|
1091
|
+
// );
|
|
829
1092
|
clusters.push({
|
|
830
1093
|
endpoint: endpoint.number.toString(),
|
|
831
1094
|
id: 'main',
|
|
@@ -838,12 +1101,18 @@ export class Frontend extends EventEmitter {
|
|
|
838
1101
|
attributeLocalValue: attributeValue,
|
|
839
1102
|
});
|
|
840
1103
|
});
|
|
1104
|
+
// Get the child endpoints
|
|
841
1105
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1106
|
+
// if (childEndpoints.length === 0) {
|
|
1107
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1108
|
+
// }
|
|
842
1109
|
childEndpoints.forEach((childEndpoint) => {
|
|
843
1110
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
844
1111
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
845
1112
|
return;
|
|
846
1113
|
}
|
|
1114
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1115
|
+
// Get the device types of the child endpoint
|
|
847
1116
|
const deviceTypes = [];
|
|
848
1117
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
849
1118
|
deviceTypes.push(d.deviceType);
|
|
@@ -853,9 +1122,12 @@ export class Frontend extends EventEmitter {
|
|
|
853
1122
|
return;
|
|
854
1123
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
855
1124
|
return;
|
|
1125
|
+
// console.log(
|
|
1126
|
+
// `${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}`,
|
|
1127
|
+
// );
|
|
856
1128
|
clusters.push({
|
|
857
1129
|
endpoint: childEndpoint.number.toString(),
|
|
858
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1130
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
859
1131
|
deviceTypes,
|
|
860
1132
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
861
1133
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -868,6 +1140,13 @@ export class Frontend extends EventEmitter {
|
|
|
868
1140
|
});
|
|
869
1141
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
870
1142
|
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1147
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1148
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1149
|
+
*/
|
|
871
1150
|
async wsMessageHandler(client, message) {
|
|
872
1151
|
let data;
|
|
873
1152
|
try {
|
|
@@ -914,32 +1193,41 @@ export class Frontend extends EventEmitter {
|
|
|
914
1193
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
915
1194
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
916
1195
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1196
|
+
// The install comes from InstallPlugins
|
|
917
1197
|
this.matterbridge.plugins
|
|
918
1198
|
.add(packageName)
|
|
919
1199
|
.then((plugin) => {
|
|
920
1200
|
if (plugin) {
|
|
1201
|
+
// The plugin is not registered
|
|
921
1202
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
922
1203
|
this.matterbridge.plugins
|
|
923
1204
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1205
|
+
// eslint-disable-next-line promise/no-nesting
|
|
924
1206
|
.then(() => {
|
|
925
1207
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
926
1208
|
this.wssSendRefreshRequired('plugins');
|
|
927
1209
|
return;
|
|
928
1210
|
})
|
|
1211
|
+
// eslint-disable-next-line promise/no-nesting
|
|
929
1212
|
.catch((_error) => {
|
|
1213
|
+
//
|
|
930
1214
|
});
|
|
931
1215
|
}
|
|
932
1216
|
else {
|
|
1217
|
+
// The plugin is already registered
|
|
933
1218
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
934
1219
|
this.wssSendRefreshRequired('plugins');
|
|
935
1220
|
this.wssSendRestartRequired();
|
|
936
1221
|
}
|
|
937
1222
|
return;
|
|
938
1223
|
})
|
|
1224
|
+
// eslint-disable-next-line promise/no-nesting
|
|
939
1225
|
.catch((_error) => {
|
|
1226
|
+
//
|
|
940
1227
|
});
|
|
941
1228
|
}
|
|
942
1229
|
else {
|
|
1230
|
+
// The package is matterbridge
|
|
943
1231
|
if (this.matterbridge.restartMode !== '') {
|
|
944
1232
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
945
1233
|
this.matterbridge.shutdownProcess();
|
|
@@ -962,6 +1250,7 @@ export class Frontend extends EventEmitter {
|
|
|
962
1250
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
963
1251
|
return;
|
|
964
1252
|
}
|
|
1253
|
+
// The package is a plugin
|
|
965
1254
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
966
1255
|
if (plugin) {
|
|
967
1256
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -970,6 +1259,7 @@ export class Frontend extends EventEmitter {
|
|
|
970
1259
|
this.wssSendRefreshRequired('plugins');
|
|
971
1260
|
this.wssSendRefreshRequired('devices');
|
|
972
1261
|
}
|
|
1262
|
+
// Uninstall the package
|
|
973
1263
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
974
1264
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
975
1265
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1010,6 +1300,7 @@ export class Frontend extends EventEmitter {
|
|
|
1010
1300
|
return;
|
|
1011
1301
|
})
|
|
1012
1302
|
.catch((_error) => {
|
|
1303
|
+
//
|
|
1013
1304
|
});
|
|
1014
1305
|
}
|
|
1015
1306
|
else {
|
|
@@ -1056,6 +1347,7 @@ export class Frontend extends EventEmitter {
|
|
|
1056
1347
|
return;
|
|
1057
1348
|
})
|
|
1058
1349
|
.catch((_error) => {
|
|
1350
|
+
//
|
|
1059
1351
|
});
|
|
1060
1352
|
}
|
|
1061
1353
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1293,22 +1585,22 @@ export class Frontend extends EventEmitter {
|
|
|
1293
1585
|
if (isValidString(data.params.value, 4)) {
|
|
1294
1586
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1295
1587
|
if (data.params.value === 'Debug') {
|
|
1296
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1588
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1297
1589
|
}
|
|
1298
1590
|
else if (data.params.value === 'Info') {
|
|
1299
|
-
await this.matterbridge.setLogLevel("info");
|
|
1591
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1300
1592
|
}
|
|
1301
1593
|
else if (data.params.value === 'Notice') {
|
|
1302
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1594
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1303
1595
|
}
|
|
1304
1596
|
else if (data.params.value === 'Warn') {
|
|
1305
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1597
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1306
1598
|
}
|
|
1307
1599
|
else if (data.params.value === 'Error') {
|
|
1308
|
-
await this.matterbridge.setLogLevel("error");
|
|
1600
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1309
1601
|
}
|
|
1310
1602
|
else if (data.params.value === 'Fatal') {
|
|
1311
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1603
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1312
1604
|
}
|
|
1313
1605
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1314
1606
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1319,6 +1611,7 @@ export class Frontend extends EventEmitter {
|
|
|
1319
1611
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1320
1612
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1321
1613
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1614
|
+
// Create the file logger for matterbridge
|
|
1322
1615
|
if (data.params.value)
|
|
1323
1616
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1324
1617
|
else
|
|
@@ -1483,15 +1776,19 @@ export class Frontend extends EventEmitter {
|
|
|
1483
1776
|
return;
|
|
1484
1777
|
}
|
|
1485
1778
|
const config = plugin.configJson;
|
|
1779
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1486
1780
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1781
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1487
1782
|
if (select === 'serial')
|
|
1488
1783
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1489
1784
|
if (select === 'name')
|
|
1490
1785
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1491
1786
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1787
|
+
// Remove postfix from the serial if it exists
|
|
1492
1788
|
if (config.postfix) {
|
|
1493
1789
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1494
1790
|
}
|
|
1791
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1495
1792
|
if (isValidArray(config.whiteList, 1)) {
|
|
1496
1793
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1497
1794
|
config.whiteList.push(data.params.serial);
|
|
@@ -1500,6 +1797,7 @@ export class Frontend extends EventEmitter {
|
|
|
1500
1797
|
config.whiteList.push(data.params.name);
|
|
1501
1798
|
}
|
|
1502
1799
|
}
|
|
1800
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1503
1801
|
if (isValidArray(config.blackList, 1)) {
|
|
1504
1802
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1505
1803
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1529,7 +1827,9 @@ export class Frontend extends EventEmitter {
|
|
|
1529
1827
|
return;
|
|
1530
1828
|
}
|
|
1531
1829
|
const config = plugin.configJson;
|
|
1830
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1532
1831
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1832
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1533
1833
|
if (select === 'serial')
|
|
1534
1834
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1535
1835
|
if (select === 'name')
|
|
@@ -1538,6 +1838,7 @@ export class Frontend extends EventEmitter {
|
|
|
1538
1838
|
if (config.postfix) {
|
|
1539
1839
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1540
1840
|
}
|
|
1841
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1541
1842
|
if (isValidArray(config.whiteList, 1)) {
|
|
1542
1843
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1543
1844
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1546,6 +1847,7 @@ export class Frontend extends EventEmitter {
|
|
|
1546
1847
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1547
1848
|
}
|
|
1548
1849
|
}
|
|
1850
|
+
// Add the serial to the blackList
|
|
1549
1851
|
if (isValidArray(config.blackList)) {
|
|
1550
1852
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1551
1853
|
config.blackList.push(data.params.serial);
|
|
@@ -1578,114 +1880,230 @@ export class Frontend extends EventEmitter {
|
|
|
1578
1880
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1579
1881
|
}
|
|
1580
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1885
|
+
*
|
|
1886
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1887
|
+
* @param {string} time - The time string of the message
|
|
1888
|
+
* @param {string} name - The logger name of the message
|
|
1889
|
+
* @param {string} message - The content of the message.
|
|
1890
|
+
*
|
|
1891
|
+
* @remarks
|
|
1892
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1893
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1894
|
+
* The function sends the message to all connected clients.
|
|
1895
|
+
*/
|
|
1581
1896
|
wssSendMessage(level, time, name, message) {
|
|
1582
1897
|
if (!level || !time || !name || !message)
|
|
1583
1898
|
return;
|
|
1899
|
+
// Remove ANSI escape codes from the message
|
|
1900
|
+
// eslint-disable-next-line no-control-regex
|
|
1584
1901
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1902
|
+
// Remove leading asterisks from the message
|
|
1585
1903
|
message = message.replace(/^\*+/, '');
|
|
1904
|
+
// Replace all occurrences of \t and \n
|
|
1586
1905
|
message = message.replace(/[\t\n]/g, '');
|
|
1906
|
+
// Remove non-printable characters
|
|
1907
|
+
// eslint-disable-next-line no-control-regex
|
|
1587
1908
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1909
|
+
// Replace all occurrences of \" with "
|
|
1588
1910
|
message = message.replace(/\\"/g, '"');
|
|
1911
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1589
1912
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1913
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1590
1914
|
const maxContinuousLength = 100;
|
|
1591
1915
|
const keepStartLength = 20;
|
|
1592
1916
|
const keepEndLength = 20;
|
|
1917
|
+
// Split the message into words
|
|
1593
1918
|
message = message
|
|
1594
1919
|
.split(' ')
|
|
1595
1920
|
.map((word) => {
|
|
1921
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1596
1922
|
if (word.length > maxContinuousLength) {
|
|
1597
1923
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1598
1924
|
}
|
|
1599
1925
|
return word;
|
|
1600
1926
|
})
|
|
1601
1927
|
.join(' ');
|
|
1928
|
+
// Send the message to all connected clients
|
|
1602
1929
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1603
1930
|
if (client.readyState === WebSocket.OPEN) {
|
|
1604
1931
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1605
1932
|
}
|
|
1606
1933
|
});
|
|
1607
1934
|
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1937
|
+
*
|
|
1938
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1939
|
+
* possible values:
|
|
1940
|
+
* - 'matterbridgeLatestVersion'
|
|
1941
|
+
* - 'matterbridgeAdvertise'
|
|
1942
|
+
* - 'online'
|
|
1943
|
+
* - 'offline'
|
|
1944
|
+
* - 'reachability'
|
|
1945
|
+
* - 'settings'
|
|
1946
|
+
* - 'plugins'
|
|
1947
|
+
* - 'pluginsRestart'
|
|
1948
|
+
* - 'devices'
|
|
1949
|
+
* - 'fabrics'
|
|
1950
|
+
* - 'sessions'
|
|
1951
|
+
*/
|
|
1608
1952
|
wssSendRefreshRequired(changed = null) {
|
|
1609
1953
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1954
|
+
// Send the message to all connected clients
|
|
1610
1955
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1611
1956
|
if (client.readyState === WebSocket.OPEN) {
|
|
1612
1957
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1613
1958
|
}
|
|
1614
1959
|
});
|
|
1615
1960
|
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1963
|
+
*
|
|
1964
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1965
|
+
*/
|
|
1616
1966
|
wssSendRestartRequired(snackbar = true) {
|
|
1617
1967
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1618
1968
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1619
1969
|
if (snackbar === true)
|
|
1620
1970
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1971
|
+
// Send the message to all connected clients
|
|
1621
1972
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1622
1973
|
if (client.readyState === WebSocket.OPEN) {
|
|
1623
1974
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1624
1975
|
}
|
|
1625
1976
|
});
|
|
1626
1977
|
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1980
|
+
*
|
|
1981
|
+
*/
|
|
1627
1982
|
wssSendUpdateRequired() {
|
|
1628
1983
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1629
1984
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1985
|
+
// Send the message to all connected clients
|
|
1630
1986
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1631
1987
|
if (client.readyState === WebSocket.OPEN) {
|
|
1632
1988
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1633
1989
|
}
|
|
1634
1990
|
});
|
|
1635
1991
|
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Sends a cpu update message to all connected clients.
|
|
1994
|
+
*
|
|
1995
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
1996
|
+
*/
|
|
1636
1997
|
wssSendCpuUpdate(cpuUsage) {
|
|
1637
1998
|
if (hasParameter('debug'))
|
|
1638
1999
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2000
|
+
// Send the message to all connected clients
|
|
1639
2001
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1640
2002
|
if (client.readyState === WebSocket.OPEN) {
|
|
1641
2003
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1642
2004
|
}
|
|
1643
2005
|
});
|
|
1644
2006
|
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Sends a memory update message to all connected clients.
|
|
2009
|
+
*
|
|
2010
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2011
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2012
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2013
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2014
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2015
|
+
* @param {string} external - The external memory in bytes.
|
|
2016
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2017
|
+
*/
|
|
1645
2018
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1646
2019
|
if (hasParameter('debug'))
|
|
1647
2020
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2021
|
+
// Send the message to all connected clients
|
|
1648
2022
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1649
2023
|
if (client.readyState === WebSocket.OPEN) {
|
|
1650
2024
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1651
2025
|
}
|
|
1652
2026
|
});
|
|
1653
2027
|
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Sends an uptime update message to all connected clients.
|
|
2030
|
+
*
|
|
2031
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2032
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2033
|
+
*/
|
|
1654
2034
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1655
2035
|
if (hasParameter('debug'))
|
|
1656
2036
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2037
|
+
// Send the message to all connected clients
|
|
1657
2038
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1658
2039
|
if (client.readyState === WebSocket.OPEN) {
|
|
1659
2040
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1660
2041
|
}
|
|
1661
2042
|
});
|
|
1662
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Sends an open snackbar message to all connected clients.
|
|
2046
|
+
*
|
|
2047
|
+
* @param {string} message - The message to send.
|
|
2048
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2049
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2050
|
+
*/
|
|
1663
2051
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1664
2052
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2053
|
+
// Send the message to all connected clients
|
|
1665
2054
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1666
2055
|
if (client.readyState === WebSocket.OPEN) {
|
|
1667
2056
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1668
2057
|
}
|
|
1669
2058
|
});
|
|
1670
2059
|
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Sends a close snackbar message to all connected clients.
|
|
2062
|
+
*
|
|
2063
|
+
* @param {string} message - The message to send.
|
|
2064
|
+
*/
|
|
1671
2065
|
wssSendCloseSnackbarMessage(message) {
|
|
1672
2066
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2067
|
+
// Send the message to all connected clients
|
|
1673
2068
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1674
2069
|
if (client.readyState === WebSocket.OPEN) {
|
|
1675
2070
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1676
2071
|
}
|
|
1677
2072
|
});
|
|
1678
2073
|
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2076
|
+
*
|
|
2077
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2078
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2079
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2080
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2081
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2082
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2083
|
+
*
|
|
2084
|
+
* @remarks
|
|
2085
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2086
|
+
* with the updated attribute information.
|
|
2087
|
+
*/
|
|
1679
2088
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1680
2089
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2090
|
+
// Send the message to all connected clients
|
|
1681
2091
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1682
2092
|
if (client.readyState === WebSocket.OPEN) {
|
|
1683
2093
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1684
2094
|
}
|
|
1685
2095
|
});
|
|
1686
2096
|
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Sends a message to all connected clients.
|
|
2099
|
+
*
|
|
2100
|
+
* @param {number} id - The message id.
|
|
2101
|
+
* @param {string} method - The message method.
|
|
2102
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2103
|
+
*/
|
|
1687
2104
|
wssBroadcastMessage(id, method, params) {
|
|
1688
2105
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2106
|
+
// Send the message to all connected clients
|
|
1689
2107
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1690
2108
|
if (client.readyState === WebSocket.OPEN) {
|
|
1691
2109
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1693,3 +2111,4 @@ export class Frontend extends EventEmitter {
|
|
|
1693
2111
|
});
|
|
1694
2112
|
}
|
|
1695
2113
|
}
|
|
2114
|
+
//# sourceMappingURL=frontend.js.map
|