matterbridge 3.1.4-dev-20250715-075e722 → 3.1.4
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 +7 -2
- package/README.md +25 -2
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- 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 +30 -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 +110 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +89 -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 +304 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +491 -49
- 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 +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -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 +444 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +785 -51
- 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 +1250 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1106 -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 +195 -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 -3
- 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 +74 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +81 -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,36 +1,131 @@
|
|
|
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.2.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
|
-
import { promises as fs } from 'node:fs';
|
|
29
|
+
import { existsSync, 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, 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, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
16
44
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
45
|
+
/**
|
|
46
|
+
* Websocket message ID for logging.
|
|
47
|
+
*
|
|
48
|
+
* @constant {number}
|
|
49
|
+
*/
|
|
17
50
|
export const WS_ID_LOG = 0;
|
|
51
|
+
/**
|
|
52
|
+
* Websocket message ID indicating a refresh is needed.
|
|
53
|
+
*
|
|
54
|
+
* @constant {number}
|
|
55
|
+
*/
|
|
18
56
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Websocket message ID indicating a restart is needed.
|
|
59
|
+
*
|
|
60
|
+
* @constant {number}
|
|
61
|
+
*/
|
|
19
62
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
63
|
+
/**
|
|
64
|
+
* Websocket message ID indicating a cpu update.
|
|
65
|
+
*
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
20
68
|
export const WS_ID_CPU_UPDATE = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
*
|
|
72
|
+
* @constant {number}
|
|
73
|
+
*/
|
|
21
74
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
75
|
+
/**
|
|
76
|
+
* Websocket message ID indicating an uptime update.
|
|
77
|
+
*
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
22
80
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
81
|
+
/**
|
|
82
|
+
* Websocket message ID indicating a snackbar message.
|
|
83
|
+
*
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
23
86
|
export const WS_ID_SNACKBAR = 6;
|
|
87
|
+
/**
|
|
88
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
89
|
+
*
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
24
92
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
93
|
+
/**
|
|
94
|
+
* Websocket message ID indicating a state update.
|
|
95
|
+
*
|
|
96
|
+
* @constant {number}
|
|
97
|
+
*/
|
|
25
98
|
export const WS_ID_STATEUPDATE = 8;
|
|
99
|
+
/**
|
|
100
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
101
|
+
*
|
|
102
|
+
* @constant {number}
|
|
103
|
+
*/
|
|
26
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
|
+
*/
|
|
27
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
|
+
*/
|
|
28
124
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
29
125
|
export class Frontend extends EventEmitter {
|
|
30
126
|
matterbridge;
|
|
31
127
|
log;
|
|
32
128
|
port = 8283;
|
|
33
|
-
initializeError = false;
|
|
34
129
|
expressApp;
|
|
35
130
|
httpServer;
|
|
36
131
|
httpsServer;
|
|
@@ -38,7 +133,7 @@ export class Frontend extends EventEmitter {
|
|
|
38
133
|
constructor(matterbridge) {
|
|
39
134
|
super();
|
|
40
135
|
this.matterbridge = matterbridge;
|
|
41
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
136
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
42
137
|
}
|
|
43
138
|
set logLevel(logLevel) {
|
|
44
139
|
this.log.logLevel = logLevel;
|
|
@@ -46,13 +141,43 @@ export class Frontend extends EventEmitter {
|
|
|
46
141
|
async start(port = 8283) {
|
|
47
142
|
this.port = port;
|
|
48
143
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
144
|
+
// Initialize multer with the upload directory
|
|
49
145
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
50
146
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
51
147
|
const upload = multer({ dest: uploadDir });
|
|
148
|
+
// Create the express app that serves the frontend
|
|
52
149
|
this.expressApp = express();
|
|
150
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
151
|
+
/*
|
|
152
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
153
|
+
for (const method of methods) {
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
158
|
+
try {
|
|
159
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
160
|
+
return original(path, ...rest);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
*/
|
|
168
|
+
// Log all requests to the server for debugging
|
|
169
|
+
/*
|
|
170
|
+
this.expressApp.use((req, res, next) => {
|
|
171
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
172
|
+
next();
|
|
173
|
+
});
|
|
174
|
+
*/
|
|
175
|
+
// Serve static files from '/static' endpoint
|
|
53
176
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
54
177
|
if (!hasParameter('ssl')) {
|
|
178
|
+
// Create an HTTP server and attach the express app
|
|
55
179
|
try {
|
|
180
|
+
this.log.debug(`Creating HTTP server...`);
|
|
56
181
|
this.httpServer = createServer(this.expressApp);
|
|
57
182
|
}
|
|
58
183
|
catch (error) {
|
|
@@ -60,6 +185,7 @@ export class Frontend extends EventEmitter {
|
|
|
60
185
|
this.emit('server_error', error);
|
|
61
186
|
return;
|
|
62
187
|
}
|
|
188
|
+
// Listen on the specified port
|
|
63
189
|
if (hasParameter('ingress')) {
|
|
64
190
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
65
191
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -85,49 +211,86 @@ export class Frontend extends EventEmitter {
|
|
|
85
211
|
this.log.error(`Port ${this.port} is already in use`);
|
|
86
212
|
break;
|
|
87
213
|
}
|
|
88
|
-
this.initializeError = true;
|
|
89
214
|
this.emit('server_error', error);
|
|
90
215
|
return;
|
|
91
216
|
});
|
|
92
217
|
}
|
|
93
218
|
else {
|
|
94
219
|
let cert;
|
|
95
|
-
try {
|
|
96
|
-
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
97
|
-
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
|
|
101
|
-
this.emit('server_error', error);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
220
|
let key;
|
|
105
|
-
try {
|
|
106
|
-
key = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
107
|
-
this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}`);
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
|
|
111
|
-
this.emit('server_error', error);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
221
|
let ca;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
222
|
+
let fullChain;
|
|
223
|
+
let pfx;
|
|
224
|
+
let passphrase;
|
|
225
|
+
let httpsServerOptions = {};
|
|
226
|
+
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
227
|
+
// Load the p12 certificate and the passphrase
|
|
228
|
+
try {
|
|
229
|
+
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
230
|
+
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
this.log.error(`Error reading p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}: ${error}`);
|
|
234
|
+
this.emit('server_error', error);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
239
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
240
|
+
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
this.log.error(`Error reading p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}: ${error}`);
|
|
244
|
+
this.emit('server_error', error);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
httpsServerOptions = { pfx, passphrase };
|
|
118
248
|
}
|
|
119
|
-
|
|
120
|
-
|
|
249
|
+
else {
|
|
250
|
+
// Load the SSL certificate, the private key and optionally the CA certificate. If the CA certificate is present, it will be used to create a full chain certificate.
|
|
251
|
+
try {
|
|
252
|
+
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
253
|
+
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
this.log.error(`Error reading certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
|
|
257
|
+
this.emit('server_error', error);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
key = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
262
|
+
this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}`);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
this.log.error(`Error reading key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
|
|
266
|
+
this.emit('server_error', error);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
ca = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
|
|
271
|
+
fullChain = `${cert}\n${ca}`;
|
|
272
|
+
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')}`);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
276
|
+
}
|
|
277
|
+
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
121
278
|
}
|
|
122
|
-
|
|
279
|
+
if (hasParameter('mtls')) {
|
|
280
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
281
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
282
|
+
}
|
|
283
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
123
284
|
try {
|
|
124
|
-
this.
|
|
285
|
+
this.log.debug(`Creating HTTPS server...`);
|
|
286
|
+
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
125
287
|
}
|
|
126
288
|
catch (error) {
|
|
127
289
|
this.log.error(`Failed to create HTTPS server: ${error}`);
|
|
128
290
|
this.emit('server_error', error);
|
|
129
291
|
return;
|
|
130
292
|
}
|
|
293
|
+
// Listen on the specified port
|
|
131
294
|
if (hasParameter('ingress')) {
|
|
132
295
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
133
296
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -153,23 +316,23 @@ export class Frontend extends EventEmitter {
|
|
|
153
316
|
this.log.error(`Port ${this.port} is already in use`);
|
|
154
317
|
break;
|
|
155
318
|
}
|
|
156
|
-
this.initializeError = true;
|
|
157
319
|
this.emit('server_error', error);
|
|
158
320
|
return;
|
|
159
321
|
});
|
|
160
322
|
}
|
|
161
|
-
|
|
162
|
-
return;
|
|
323
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
163
324
|
const wssPort = this.port;
|
|
164
325
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
326
|
+
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
165
327
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
166
328
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
167
329
|
const clientIp = request.socket.remoteAddress;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
330
|
+
// Set the global logger callback for the WebSocketServer
|
|
331
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
332
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
333
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
334
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
335
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
173
336
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
174
337
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
175
338
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -191,6 +354,7 @@ export class Frontend extends EventEmitter {
|
|
|
191
354
|
}
|
|
192
355
|
});
|
|
193
356
|
ws.on('error', (error) => {
|
|
357
|
+
// istanbul ignore next
|
|
194
358
|
this.log.error(`WebSocket client error: ${error}`);
|
|
195
359
|
});
|
|
196
360
|
});
|
|
@@ -204,6 +368,7 @@ export class Frontend extends EventEmitter {
|
|
|
204
368
|
this.webSocketServer.on('error', (ws, error) => {
|
|
205
369
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
206
370
|
});
|
|
371
|
+
// Subscribe to cli events
|
|
207
372
|
cliEmitter.removeAllListeners();
|
|
208
373
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
209
374
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -214,6 +379,8 @@ export class Frontend extends EventEmitter {
|
|
|
214
379
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
215
380
|
this.wssSendCpuUpdate(cpuUsage);
|
|
216
381
|
});
|
|
382
|
+
// Endpoint to validate login code
|
|
383
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
217
384
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
218
385
|
const { password } = req.body;
|
|
219
386
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -232,23 +399,27 @@ export class Frontend extends EventEmitter {
|
|
|
232
399
|
this.log.warn('/api/login error wrong password');
|
|
233
400
|
res.json({ valid: false });
|
|
234
401
|
}
|
|
402
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
235
403
|
}
|
|
236
404
|
catch (error) {
|
|
237
405
|
this.log.error('/api/login error getting password');
|
|
238
406
|
res.json({ valid: false });
|
|
239
407
|
}
|
|
240
408
|
});
|
|
409
|
+
// Endpoint to provide health check for docker
|
|
241
410
|
this.expressApp.get('/health', (req, res) => {
|
|
242
411
|
this.log.debug('Express received /health');
|
|
243
412
|
const healthStatus = {
|
|
244
|
-
status: 'ok',
|
|
245
|
-
uptime: process.uptime(),
|
|
246
|
-
timestamp: new Date().toISOString(),
|
|
413
|
+
status: 'ok', // Indicate service is healthy
|
|
414
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
415
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
247
416
|
};
|
|
248
417
|
res.status(200).json(healthStatus);
|
|
249
418
|
});
|
|
419
|
+
// Endpoint to provide memory usage details
|
|
250
420
|
this.expressApp.get('/memory', async (req, res) => {
|
|
251
421
|
this.log.debug('Express received /memory');
|
|
422
|
+
// Memory usage from process
|
|
252
423
|
const memoryUsageRaw = process.memoryUsage();
|
|
253
424
|
const memoryUsage = {
|
|
254
425
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -257,10 +428,13 @@ export class Frontend extends EventEmitter {
|
|
|
257
428
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
258
429
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
259
430
|
};
|
|
431
|
+
// V8 heap statistics
|
|
260
432
|
const { default: v8 } = await import('node:v8');
|
|
261
433
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
262
434
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
435
|
+
// Format heapStats
|
|
263
436
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
437
|
+
// Format heapSpaces
|
|
264
438
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
265
439
|
...space,
|
|
266
440
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -278,19 +452,23 @@ export class Frontend extends EventEmitter {
|
|
|
278
452
|
};
|
|
279
453
|
res.status(200).json(memoryReport);
|
|
280
454
|
});
|
|
455
|
+
// Endpoint to provide settings
|
|
281
456
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
282
457
|
this.log.debug('The frontend sent /api/settings');
|
|
283
458
|
res.json(await this.getApiSettings());
|
|
284
459
|
});
|
|
460
|
+
// Endpoint to provide plugins
|
|
285
461
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
286
462
|
this.log.debug('The frontend sent /api/plugins');
|
|
287
463
|
res.json(this.getBaseRegisteredPlugins());
|
|
288
464
|
});
|
|
465
|
+
// Endpoint to provide devices
|
|
289
466
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
290
467
|
this.log.debug('The frontend sent /api/devices');
|
|
291
468
|
const devices = await this.getDevices();
|
|
292
469
|
res.json(devices);
|
|
293
470
|
});
|
|
471
|
+
// Endpoint to view the matterbridge log
|
|
294
472
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
295
473
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
296
474
|
try {
|
|
@@ -303,6 +481,7 @@ export class Frontend extends EventEmitter {
|
|
|
303
481
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
304
482
|
}
|
|
305
483
|
});
|
|
484
|
+
// Endpoint to view the matter.js log
|
|
306
485
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
307
486
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
308
487
|
try {
|
|
@@ -315,6 +494,7 @@ export class Frontend extends EventEmitter {
|
|
|
315
494
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
316
495
|
}
|
|
317
496
|
});
|
|
497
|
+
// Endpoint to view the shelly log
|
|
318
498
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
319
499
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
320
500
|
try {
|
|
@@ -327,9 +507,11 @@ export class Frontend extends EventEmitter {
|
|
|
327
507
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
328
508
|
}
|
|
329
509
|
});
|
|
510
|
+
// Endpoint to download the matterbridge log
|
|
330
511
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
331
512
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
332
513
|
try {
|
|
514
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
333
515
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
|
|
334
516
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
|
|
335
517
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
|
|
@@ -340,15 +522,18 @@ export class Frontend extends EventEmitter {
|
|
|
340
522
|
}
|
|
341
523
|
res.type('text/plain');
|
|
342
524
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
525
|
+
/* istanbul ignore if */
|
|
343
526
|
if (error) {
|
|
344
527
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
345
528
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
346
529
|
}
|
|
347
530
|
});
|
|
348
531
|
});
|
|
532
|
+
// Endpoint to download the matter log
|
|
349
533
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
350
534
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
351
535
|
try {
|
|
536
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
352
537
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
353
538
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
354
539
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -359,15 +544,18 @@ export class Frontend extends EventEmitter {
|
|
|
359
544
|
}
|
|
360
545
|
res.type('text/plain');
|
|
361
546
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
547
|
+
/* istanbul ignore if */
|
|
362
548
|
if (error) {
|
|
363
549
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
364
550
|
res.status(500).send('Error downloading the matter log file');
|
|
365
551
|
}
|
|
366
552
|
});
|
|
367
553
|
});
|
|
554
|
+
// Endpoint to download the shelly log
|
|
368
555
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
369
556
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
370
557
|
try {
|
|
558
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
371
559
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
372
560
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
373
561
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -378,74 +566,90 @@ export class Frontend extends EventEmitter {
|
|
|
378
566
|
}
|
|
379
567
|
res.type('text/plain');
|
|
380
568
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
569
|
+
/* istanbul ignore if */
|
|
381
570
|
if (error) {
|
|
382
571
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
383
572
|
res.status(500).send('Error downloading Shelly system log file');
|
|
384
573
|
}
|
|
385
574
|
});
|
|
386
575
|
});
|
|
576
|
+
// Endpoint to download the matterbridge storage directory
|
|
387
577
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
388
578
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
389
579
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
390
580
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
581
|
+
/* istanbul ignore if */
|
|
391
582
|
if (error) {
|
|
392
583
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
393
584
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
394
585
|
}
|
|
395
586
|
});
|
|
396
587
|
});
|
|
588
|
+
// Endpoint to download the matter storage file
|
|
397
589
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
398
590
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
399
591
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
400
592
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
593
|
+
/* istanbul ignore if */
|
|
401
594
|
if (error) {
|
|
402
595
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
403
596
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
404
597
|
}
|
|
405
598
|
});
|
|
406
599
|
});
|
|
600
|
+
// Endpoint to download the matterbridge plugin directory
|
|
407
601
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
408
602
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
409
603
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
410
604
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
605
|
+
/* istanbul ignore if */
|
|
411
606
|
if (error) {
|
|
412
607
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
413
608
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
414
609
|
}
|
|
415
610
|
});
|
|
416
611
|
});
|
|
612
|
+
// Endpoint to download the matterbridge plugin config files
|
|
417
613
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
418
614
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
419
615
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
420
616
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
617
|
+
/* istanbul ignore if */
|
|
421
618
|
if (error) {
|
|
422
619
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
423
620
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
424
621
|
}
|
|
425
622
|
});
|
|
426
623
|
});
|
|
624
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
427
625
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
428
626
|
this.log.debug('The frontend sent /api/download-backup');
|
|
429
627
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
628
|
+
/* istanbul ignore if */
|
|
430
629
|
if (error) {
|
|
431
630
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
432
631
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
433
632
|
}
|
|
434
633
|
});
|
|
435
634
|
});
|
|
635
|
+
// Endpoint to upload a package
|
|
436
636
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
437
637
|
const { filename } = req.body;
|
|
438
638
|
const file = req.file;
|
|
639
|
+
/* istanbul ignore if */
|
|
439
640
|
if (!file || !filename) {
|
|
440
641
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
441
642
|
res.status(400).send('Invalid request: file and filename are required');
|
|
442
643
|
return;
|
|
443
644
|
}
|
|
444
645
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
646
|
+
// Define the path where the plugin file will be saved
|
|
445
647
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
446
648
|
try {
|
|
649
|
+
// Move the uploaded file to the specified path
|
|
447
650
|
await fs.rename(file.path, filePath);
|
|
448
651
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
652
|
+
// Install the plugin package
|
|
449
653
|
if (filename.endsWith('.tgz')) {
|
|
450
654
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
451
655
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -465,6 +669,7 @@ export class Frontend extends EventEmitter {
|
|
|
465
669
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
466
670
|
}
|
|
467
671
|
});
|
|
672
|
+
// Fallback for routing (must be the last route)
|
|
468
673
|
this.expressApp.use((req, res) => {
|
|
469
674
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
470
675
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -473,13 +678,16 @@ export class Frontend extends EventEmitter {
|
|
|
473
678
|
}
|
|
474
679
|
async stop() {
|
|
475
680
|
this.log.debug('Stopping the frontend...');
|
|
681
|
+
// Remove listeners from the express app
|
|
476
682
|
if (this.expressApp) {
|
|
477
683
|
this.expressApp.removeAllListeners();
|
|
478
684
|
this.expressApp = undefined;
|
|
479
685
|
this.log.debug('Frontend app closed successfully');
|
|
480
686
|
}
|
|
687
|
+
// Close the WebSocket server
|
|
481
688
|
if (this.webSocketServer) {
|
|
482
689
|
this.log.debug('Closing WebSocket server...');
|
|
690
|
+
// Close all active connections
|
|
483
691
|
this.webSocketServer.clients.forEach((client) => {
|
|
484
692
|
if (client.readyState === WebSocket.OPEN) {
|
|
485
693
|
client.close();
|
|
@@ -492,6 +700,7 @@ export class Frontend extends EventEmitter {
|
|
|
492
700
|
}
|
|
493
701
|
else {
|
|
494
702
|
this.log.debug('WebSocket server closed successfully');
|
|
703
|
+
this.emit('websocket_server_stopped');
|
|
495
704
|
}
|
|
496
705
|
resolve();
|
|
497
706
|
});
|
|
@@ -499,6 +708,7 @@ export class Frontend extends EventEmitter {
|
|
|
499
708
|
this.webSocketServer.removeAllListeners();
|
|
500
709
|
this.webSocketServer = undefined;
|
|
501
710
|
}
|
|
711
|
+
// Close the http server
|
|
502
712
|
if (this.httpServer) {
|
|
503
713
|
this.log.debug('Closing http server...');
|
|
504
714
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -508,6 +718,7 @@ export class Frontend extends EventEmitter {
|
|
|
508
718
|
}
|
|
509
719
|
else {
|
|
510
720
|
this.log.debug('Http server closed successfully');
|
|
721
|
+
this.emit('server_stopped');
|
|
511
722
|
}
|
|
512
723
|
resolve();
|
|
513
724
|
});
|
|
@@ -516,6 +727,7 @@ export class Frontend extends EventEmitter {
|
|
|
516
727
|
this.httpServer = undefined;
|
|
517
728
|
this.log.debug('Frontend http server closed successfully');
|
|
518
729
|
}
|
|
730
|
+
// Close the https server
|
|
519
731
|
if (this.httpsServer) {
|
|
520
732
|
this.log.debug('Closing https server...');
|
|
521
733
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -525,6 +737,7 @@ export class Frontend extends EventEmitter {
|
|
|
525
737
|
}
|
|
526
738
|
else {
|
|
527
739
|
this.log.debug('Https server closed successfully');
|
|
740
|
+
this.emit('server_stopped');
|
|
528
741
|
}
|
|
529
742
|
resolve();
|
|
530
743
|
});
|
|
@@ -535,6 +748,7 @@ export class Frontend extends EventEmitter {
|
|
|
535
748
|
}
|
|
536
749
|
this.log.debug('Frontend stopped successfully');
|
|
537
750
|
}
|
|
751
|
+
// Function to format bytes to KB, MB, or GB
|
|
538
752
|
formatMemoryUsage = (bytes) => {
|
|
539
753
|
if (bytes >= 1024 ** 3) {
|
|
540
754
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -546,6 +760,7 @@ export class Frontend extends EventEmitter {
|
|
|
546
760
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
547
761
|
}
|
|
548
762
|
};
|
|
763
|
+
// Function to format system uptime with only the most significant unit
|
|
549
764
|
formatOsUpTime = (seconds) => {
|
|
550
765
|
if (seconds >= 86400) {
|
|
551
766
|
const days = Math.floor(seconds / 86400);
|
|
@@ -561,7 +776,13 @@ export class Frontend extends EventEmitter {
|
|
|
561
776
|
}
|
|
562
777
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
563
778
|
};
|
|
779
|
+
/**
|
|
780
|
+
* Retrieves the api settings data.
|
|
781
|
+
*
|
|
782
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
783
|
+
*/
|
|
564
784
|
async getApiSettings() {
|
|
785
|
+
// Update the system information
|
|
565
786
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
566
787
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
567
788
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -570,6 +791,7 @@ export class Frontend extends EventEmitter {
|
|
|
570
791
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
571
792
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
572
793
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
794
|
+
// Update the matterbridge information
|
|
573
795
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
574
796
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
575
797
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -581,6 +803,7 @@ export class Frontend extends EventEmitter {
|
|
|
581
803
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
582
804
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
583
805
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
806
|
+
// Update the matterbridge information in bridge mode
|
|
584
807
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode) {
|
|
585
808
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
586
809
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -590,6 +813,12 @@ export class Frontend extends EventEmitter {
|
|
|
590
813
|
}
|
|
591
814
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
592
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Retrieves the reachable attribute.
|
|
818
|
+
*
|
|
819
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
820
|
+
* @returns {boolean} The reachable attribute.
|
|
821
|
+
*/
|
|
593
822
|
getReachability(device) {
|
|
594
823
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
595
824
|
return false;
|
|
@@ -601,6 +830,12 @@ export class Frontend extends EventEmitter {
|
|
|
601
830
|
return true;
|
|
602
831
|
return false;
|
|
603
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Retrieves the power source attribute.
|
|
835
|
+
*
|
|
836
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
837
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
838
|
+
*/
|
|
604
839
|
getPowerSource(endpoint) {
|
|
605
840
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
606
841
|
return undefined;
|
|
@@ -616,13 +851,21 @@ export class Frontend extends EventEmitter {
|
|
|
616
851
|
}
|
|
617
852
|
return;
|
|
618
853
|
};
|
|
854
|
+
// Root endpoint
|
|
619
855
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
620
856
|
return powerSource(endpoint);
|
|
857
|
+
// Child endpoints
|
|
621
858
|
for (const child of endpoint.getChildEndpoints()) {
|
|
622
859
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
623
860
|
return powerSource(child);
|
|
624
861
|
}
|
|
625
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
865
|
+
*
|
|
866
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
867
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
868
|
+
*/
|
|
626
869
|
getMatterDataFromDevice(device) {
|
|
627
870
|
if (device.mode === 'server' && device.serverNode) {
|
|
628
871
|
return {
|
|
@@ -634,6 +877,13 @@ export class Frontend extends EventEmitter {
|
|
|
634
877
|
};
|
|
635
878
|
}
|
|
636
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Retrieves the cluster text description from a given device.
|
|
882
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
883
|
+
*
|
|
884
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
885
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
886
|
+
*/
|
|
637
887
|
getClusterTextFromDevice(device) {
|
|
638
888
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
639
889
|
return '';
|
|
@@ -658,6 +908,7 @@ export class Frontend extends EventEmitter {
|
|
|
658
908
|
let attributes = '';
|
|
659
909
|
let supportedModes = [];
|
|
660
910
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
911
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
661
912
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
662
913
|
return;
|
|
663
914
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -747,11 +998,17 @@ export class Frontend extends EventEmitter {
|
|
|
747
998
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
748
999
|
attributes += `${getUserLabel(device)} `;
|
|
749
1000
|
});
|
|
1001
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
750
1002
|
return attributes.trimStart().trimEnd();
|
|
751
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
1006
|
+
*
|
|
1007
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1008
|
+
*/
|
|
752
1009
|
getBaseRegisteredPlugins() {
|
|
753
1010
|
if (this.matterbridge.hasCleanupStarted)
|
|
754
|
-
return [];
|
|
1011
|
+
return []; // Skip if cleanup has started
|
|
755
1012
|
const baseRegisteredPlugins = [];
|
|
756
1013
|
for (const plugin of this.matterbridge.plugins) {
|
|
757
1014
|
baseRegisteredPlugins.push({
|
|
@@ -780,6 +1037,7 @@ export class Frontend extends EventEmitter {
|
|
|
780
1037
|
schemaJson: plugin.schemaJson,
|
|
781
1038
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
782
1039
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1040
|
+
// Childbridge mode specific data
|
|
783
1041
|
paired: plugin.serverNode?.state.commissioning.commissioned,
|
|
784
1042
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.qrPairingCode,
|
|
785
1043
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.manualPairingCode,
|
|
@@ -789,13 +1047,21 @@ export class Frontend extends EventEmitter {
|
|
|
789
1047
|
}
|
|
790
1048
|
return baseRegisteredPlugins;
|
|
791
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Retrieves the devices from Matterbridge.
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1054
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1055
|
+
*/
|
|
792
1056
|
async getDevices(pluginName) {
|
|
793
1057
|
if (this.matterbridge.hasCleanupStarted)
|
|
794
|
-
return [];
|
|
1058
|
+
return []; // Skip if cleanup has started
|
|
795
1059
|
const devices = [];
|
|
796
1060
|
for (const device of this.matterbridge.devices.array()) {
|
|
1061
|
+
// Filter by pluginName if provided
|
|
797
1062
|
if (pluginName && pluginName !== device.plugin)
|
|
798
1063
|
continue;
|
|
1064
|
+
// Check if the device has the required properties
|
|
799
1065
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
800
1066
|
continue;
|
|
801
1067
|
devices.push({
|
|
@@ -815,22 +1081,37 @@ export class Frontend extends EventEmitter {
|
|
|
815
1081
|
}
|
|
816
1082
|
return devices;
|
|
817
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1086
|
+
*
|
|
1087
|
+
* Response for /api/clusters
|
|
1088
|
+
*
|
|
1089
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1090
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1091
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1092
|
+
*/
|
|
818
1093
|
getClusters(pluginName, endpointNumber) {
|
|
819
1094
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
820
1095
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
821
1096
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
822
1097
|
return;
|
|
823
1098
|
}
|
|
1099
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1100
|
+
// Get the device types from the main endpoint
|
|
824
1101
|
const deviceTypes = [];
|
|
825
1102
|
const clusters = [];
|
|
826
1103
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
827
1104
|
deviceTypes.push(d.deviceType);
|
|
828
1105
|
});
|
|
1106
|
+
// Get the clusters from the main endpoint
|
|
829
1107
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
830
1108
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
831
1109
|
return;
|
|
832
1110
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
833
1111
|
return;
|
|
1112
|
+
// console.log(
|
|
1113
|
+
// `${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}`,
|
|
1114
|
+
// );
|
|
834
1115
|
clusters.push({
|
|
835
1116
|
endpoint: endpoint.number.toString(),
|
|
836
1117
|
id: 'main',
|
|
@@ -843,12 +1124,18 @@ export class Frontend extends EventEmitter {
|
|
|
843
1124
|
attributeLocalValue: attributeValue,
|
|
844
1125
|
});
|
|
845
1126
|
});
|
|
1127
|
+
// Get the child endpoints
|
|
846
1128
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1129
|
+
// if (childEndpoints.length === 0) {
|
|
1130
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1131
|
+
// }
|
|
847
1132
|
childEndpoints.forEach((childEndpoint) => {
|
|
848
1133
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
849
1134
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
850
1135
|
return;
|
|
851
1136
|
}
|
|
1137
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1138
|
+
// Get the device types of the child endpoint
|
|
852
1139
|
const deviceTypes = [];
|
|
853
1140
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
854
1141
|
deviceTypes.push(d.deviceType);
|
|
@@ -858,9 +1145,12 @@ export class Frontend extends EventEmitter {
|
|
|
858
1145
|
return;
|
|
859
1146
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
860
1147
|
return;
|
|
1148
|
+
// console.log(
|
|
1149
|
+
// `${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}`,
|
|
1150
|
+
// );
|
|
861
1151
|
clusters.push({
|
|
862
1152
|
endpoint: childEndpoint.number.toString(),
|
|
863
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1153
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
864
1154
|
deviceTypes,
|
|
865
1155
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
866
1156
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -873,6 +1163,13 @@ export class Frontend extends EventEmitter {
|
|
|
873
1163
|
});
|
|
874
1164
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
875
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1168
|
+
*
|
|
1169
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1170
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1171
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1172
|
+
*/
|
|
876
1173
|
async wsMessageHandler(client, message) {
|
|
877
1174
|
let data;
|
|
878
1175
|
try {
|
|
@@ -919,32 +1216,42 @@ export class Frontend extends EventEmitter {
|
|
|
919
1216
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
920
1217
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
921
1218
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1219
|
+
// The install comes from InstallPlugins
|
|
922
1220
|
this.matterbridge.plugins
|
|
923
1221
|
.add(packageName)
|
|
924
1222
|
.then((plugin) => {
|
|
925
1223
|
if (plugin) {
|
|
1224
|
+
// The plugin is not registered
|
|
926
1225
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
927
1226
|
this.matterbridge.plugins
|
|
928
1227
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1228
|
+
// eslint-disable-next-line promise/no-nesting
|
|
929
1229
|
.then(() => {
|
|
930
1230
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
931
1231
|
this.wssSendRefreshRequired('plugins');
|
|
932
1232
|
return;
|
|
933
1233
|
})
|
|
1234
|
+
// eslint-disable-next-line promise/no-nesting
|
|
934
1235
|
.catch((_error) => {
|
|
1236
|
+
//
|
|
935
1237
|
});
|
|
936
1238
|
}
|
|
937
1239
|
else {
|
|
1240
|
+
// The plugin is already registered
|
|
938
1241
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
939
1242
|
this.wssSendRefreshRequired('plugins');
|
|
940
1243
|
this.wssSendRestartRequired();
|
|
941
1244
|
}
|
|
942
1245
|
return;
|
|
943
1246
|
})
|
|
1247
|
+
// eslint-disable-next-line promise/no-nesting
|
|
944
1248
|
.catch((_error) => {
|
|
1249
|
+
//
|
|
945
1250
|
});
|
|
946
1251
|
}
|
|
947
1252
|
else {
|
|
1253
|
+
// The package is matterbridge
|
|
1254
|
+
// istanbul ignore next if
|
|
948
1255
|
if (this.matterbridge.restartMode !== '') {
|
|
949
1256
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
950
1257
|
this.matterbridge.shutdownProcess();
|
|
@@ -967,6 +1274,7 @@ export class Frontend extends EventEmitter {
|
|
|
967
1274
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
968
1275
|
return;
|
|
969
1276
|
}
|
|
1277
|
+
// The package is a plugin
|
|
970
1278
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
971
1279
|
if (plugin) {
|
|
972
1280
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -975,6 +1283,7 @@ export class Frontend extends EventEmitter {
|
|
|
975
1283
|
this.wssSendRefreshRequired('plugins');
|
|
976
1284
|
this.wssSendRefreshRequired('devices');
|
|
977
1285
|
}
|
|
1286
|
+
// Uninstall the package
|
|
978
1287
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
979
1288
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
980
1289
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1015,6 +1324,7 @@ export class Frontend extends EventEmitter {
|
|
|
1015
1324
|
return;
|
|
1016
1325
|
})
|
|
1017
1326
|
.catch((_error) => {
|
|
1327
|
+
//
|
|
1018
1328
|
});
|
|
1019
1329
|
}
|
|
1020
1330
|
else {
|
|
@@ -1061,6 +1371,7 @@ export class Frontend extends EventEmitter {
|
|
|
1061
1371
|
return;
|
|
1062
1372
|
})
|
|
1063
1373
|
.catch((_error) => {
|
|
1374
|
+
//
|
|
1064
1375
|
});
|
|
1065
1376
|
}
|
|
1066
1377
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1171,6 +1482,8 @@ export class Frontend extends EventEmitter {
|
|
|
1171
1482
|
else if (data.method === '/api/advertise') {
|
|
1172
1483
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1173
1484
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1485
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1486
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1174
1487
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1175
1488
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1176
1489
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1293,22 +1606,22 @@ export class Frontend extends EventEmitter {
|
|
|
1293
1606
|
if (isValidString(data.params.value, 4)) {
|
|
1294
1607
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1295
1608
|
if (data.params.value === 'Debug') {
|
|
1296
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1609
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1297
1610
|
}
|
|
1298
1611
|
else if (data.params.value === 'Info') {
|
|
1299
|
-
await this.matterbridge.setLogLevel("info");
|
|
1612
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1300
1613
|
}
|
|
1301
1614
|
else if (data.params.value === 'Notice') {
|
|
1302
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1615
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1303
1616
|
}
|
|
1304
1617
|
else if (data.params.value === 'Warn') {
|
|
1305
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1618
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1306
1619
|
}
|
|
1307
1620
|
else if (data.params.value === 'Error') {
|
|
1308
|
-
await this.matterbridge.setLogLevel("error");
|
|
1621
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1309
1622
|
}
|
|
1310
1623
|
else if (data.params.value === 'Fatal') {
|
|
1311
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1624
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1312
1625
|
}
|
|
1313
1626
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1314
1627
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1319,6 +1632,7 @@ export class Frontend extends EventEmitter {
|
|
|
1319
1632
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1320
1633
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1321
1634
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1635
|
+
// Create the file logger for matterbridge
|
|
1322
1636
|
if (data.params.value)
|
|
1323
1637
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1324
1638
|
else
|
|
@@ -1365,6 +1679,7 @@ export class Frontend extends EventEmitter {
|
|
|
1365
1679
|
});
|
|
1366
1680
|
}
|
|
1367
1681
|
catch (error) {
|
|
1682
|
+
/* istanbul ignore next */
|
|
1368
1683
|
this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1369
1684
|
}
|
|
1370
1685
|
}
|
|
@@ -1373,6 +1688,7 @@ export class Frontend extends EventEmitter {
|
|
|
1373
1688
|
Logger.removeLogger('matterfilelogger');
|
|
1374
1689
|
}
|
|
1375
1690
|
catch (error) {
|
|
1691
|
+
/* istanbul ignore next */
|
|
1376
1692
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1377
1693
|
}
|
|
1378
1694
|
}
|
|
@@ -1485,15 +1801,19 @@ export class Frontend extends EventEmitter {
|
|
|
1485
1801
|
return;
|
|
1486
1802
|
}
|
|
1487
1803
|
const config = plugin.configJson;
|
|
1804
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1488
1805
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1806
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1489
1807
|
if (select === 'serial')
|
|
1490
1808
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1491
1809
|
if (select === 'name')
|
|
1492
1810
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1493
1811
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1812
|
+
// Remove postfix from the serial if it exists
|
|
1494
1813
|
if (config.postfix) {
|
|
1495
1814
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1496
1815
|
}
|
|
1816
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1497
1817
|
if (isValidArray(config.whiteList, 1)) {
|
|
1498
1818
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1499
1819
|
config.whiteList.push(data.params.serial);
|
|
@@ -1502,6 +1822,7 @@ export class Frontend extends EventEmitter {
|
|
|
1502
1822
|
config.whiteList.push(data.params.name);
|
|
1503
1823
|
}
|
|
1504
1824
|
}
|
|
1825
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1505
1826
|
if (isValidArray(config.blackList, 1)) {
|
|
1506
1827
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1507
1828
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1532,7 +1853,9 @@ export class Frontend extends EventEmitter {
|
|
|
1532
1853
|
return;
|
|
1533
1854
|
}
|
|
1534
1855
|
const config = plugin.configJson;
|
|
1856
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1535
1857
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1858
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1536
1859
|
if (select === 'serial')
|
|
1537
1860
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1538
1861
|
if (select === 'name')
|
|
@@ -1541,6 +1864,7 @@ export class Frontend extends EventEmitter {
|
|
|
1541
1864
|
if (config.postfix) {
|
|
1542
1865
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1543
1866
|
}
|
|
1867
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1544
1868
|
if (isValidArray(config.whiteList, 1)) {
|
|
1545
1869
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1546
1870
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1549,6 +1873,7 @@ export class Frontend extends EventEmitter {
|
|
|
1549
1873
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1550
1874
|
}
|
|
1551
1875
|
}
|
|
1876
|
+
// Add the serial to the blackList
|
|
1552
1877
|
if (isValidArray(config.blackList)) {
|
|
1553
1878
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1554
1879
|
config.blackList.push(data.params.serial);
|
|
@@ -1582,114 +1907,230 @@ export class Frontend extends EventEmitter {
|
|
|
1582
1907
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1583
1908
|
}
|
|
1584
1909
|
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1912
|
+
*
|
|
1913
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1914
|
+
* @param {string} time - The time string of the message
|
|
1915
|
+
* @param {string} name - The logger name of the message
|
|
1916
|
+
* @param {string} message - The content of the message.
|
|
1917
|
+
*
|
|
1918
|
+
* @remarks
|
|
1919
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1920
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1921
|
+
* The function sends the message to all connected clients.
|
|
1922
|
+
*/
|
|
1585
1923
|
wssSendMessage(level, time, name, message) {
|
|
1586
1924
|
if (!level || !time || !name || !message)
|
|
1587
1925
|
return;
|
|
1926
|
+
// Remove ANSI escape codes from the message
|
|
1927
|
+
// eslint-disable-next-line no-control-regex
|
|
1588
1928
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1929
|
+
// Remove leading asterisks from the message
|
|
1589
1930
|
message = message.replace(/^\*+/, '');
|
|
1931
|
+
// Replace all occurrences of \t and \n
|
|
1590
1932
|
message = message.replace(/[\t\n]/g, '');
|
|
1933
|
+
// Remove non-printable characters
|
|
1934
|
+
// eslint-disable-next-line no-control-regex
|
|
1591
1935
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1936
|
+
// Replace all occurrences of \" with "
|
|
1592
1937
|
message = message.replace(/\\"/g, '"');
|
|
1938
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1593
1939
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1940
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1594
1941
|
const maxContinuousLength = 100;
|
|
1595
1942
|
const keepStartLength = 20;
|
|
1596
1943
|
const keepEndLength = 20;
|
|
1944
|
+
// Split the message into words
|
|
1597
1945
|
message = message
|
|
1598
1946
|
.split(' ')
|
|
1599
1947
|
.map((word) => {
|
|
1948
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1600
1949
|
if (word.length > maxContinuousLength) {
|
|
1601
1950
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1602
1951
|
}
|
|
1603
1952
|
return word;
|
|
1604
1953
|
})
|
|
1605
1954
|
.join(' ');
|
|
1955
|
+
// Send the message to all connected clients
|
|
1606
1956
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1607
1957
|
if (client.readyState === WebSocket.OPEN) {
|
|
1608
1958
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1609
1959
|
}
|
|
1610
1960
|
});
|
|
1611
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1964
|
+
*
|
|
1965
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1966
|
+
* possible values:
|
|
1967
|
+
* - 'matterbridgeLatestVersion'
|
|
1968
|
+
* - 'matterbridgeAdvertise'
|
|
1969
|
+
* - 'online'
|
|
1970
|
+
* - 'offline'
|
|
1971
|
+
* - 'reachability'
|
|
1972
|
+
* - 'settings'
|
|
1973
|
+
* - 'plugins'
|
|
1974
|
+
* - 'pluginsRestart'
|
|
1975
|
+
* - 'devices'
|
|
1976
|
+
* - 'fabrics'
|
|
1977
|
+
* - 'sessions'
|
|
1978
|
+
*/
|
|
1612
1979
|
wssSendRefreshRequired(changed = null) {
|
|
1613
1980
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1981
|
+
// Send the message to all connected clients
|
|
1614
1982
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1615
1983
|
if (client.readyState === WebSocket.OPEN) {
|
|
1616
1984
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1617
1985
|
}
|
|
1618
1986
|
});
|
|
1619
1987
|
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1990
|
+
*
|
|
1991
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1992
|
+
*/
|
|
1620
1993
|
wssSendRestartRequired(snackbar = true) {
|
|
1621
1994
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1622
1995
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1623
1996
|
if (snackbar === true)
|
|
1624
1997
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1998
|
+
// Send the message to all connected clients
|
|
1625
1999
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1626
2000
|
if (client.readyState === WebSocket.OPEN) {
|
|
1627
2001
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1628
2002
|
}
|
|
1629
2003
|
});
|
|
1630
2004
|
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2007
|
+
*
|
|
2008
|
+
*/
|
|
1631
2009
|
wssSendUpdateRequired() {
|
|
1632
2010
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1633
2011
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2012
|
+
// Send the message to all connected clients
|
|
1634
2013
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1635
2014
|
if (client.readyState === WebSocket.OPEN) {
|
|
1636
2015
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1637
2016
|
}
|
|
1638
2017
|
});
|
|
1639
2018
|
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Sends a cpu update message to all connected clients.
|
|
2021
|
+
*
|
|
2022
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2023
|
+
*/
|
|
1640
2024
|
wssSendCpuUpdate(cpuUsage) {
|
|
1641
2025
|
if (hasParameter('debug'))
|
|
1642
2026
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2027
|
+
// Send the message to all connected clients
|
|
1643
2028
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1644
2029
|
if (client.readyState === WebSocket.OPEN) {
|
|
1645
2030
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1646
2031
|
}
|
|
1647
2032
|
});
|
|
1648
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Sends a memory update message to all connected clients.
|
|
2036
|
+
*
|
|
2037
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2038
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2039
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2040
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2041
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2042
|
+
* @param {string} external - The external memory in bytes.
|
|
2043
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2044
|
+
*/
|
|
1649
2045
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1650
2046
|
if (hasParameter('debug'))
|
|
1651
2047
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2048
|
+
// Send the message to all connected clients
|
|
1652
2049
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1653
2050
|
if (client.readyState === WebSocket.OPEN) {
|
|
1654
2051
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1655
2052
|
}
|
|
1656
2053
|
});
|
|
1657
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Sends an uptime update message to all connected clients.
|
|
2057
|
+
*
|
|
2058
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2059
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2060
|
+
*/
|
|
1658
2061
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1659
2062
|
if (hasParameter('debug'))
|
|
1660
2063
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2064
|
+
// Send the message to all connected clients
|
|
1661
2065
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1662
2066
|
if (client.readyState === WebSocket.OPEN) {
|
|
1663
2067
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1664
2068
|
}
|
|
1665
2069
|
});
|
|
1666
2070
|
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Sends an open snackbar message to all connected clients.
|
|
2073
|
+
*
|
|
2074
|
+
* @param {string} message - The message to send.
|
|
2075
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2076
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2077
|
+
*/
|
|
1667
2078
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1668
2079
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2080
|
+
// Send the message to all connected clients
|
|
1669
2081
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1670
2082
|
if (client.readyState === WebSocket.OPEN) {
|
|
1671
2083
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1672
2084
|
}
|
|
1673
2085
|
});
|
|
1674
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Sends a close snackbar message to all connected clients.
|
|
2089
|
+
*
|
|
2090
|
+
* @param {string} message - The message to send.
|
|
2091
|
+
*/
|
|
1675
2092
|
wssSendCloseSnackbarMessage(message) {
|
|
1676
2093
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2094
|
+
// Send the message to all connected clients
|
|
1677
2095
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1678
2096
|
if (client.readyState === WebSocket.OPEN) {
|
|
1679
2097
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1680
2098
|
}
|
|
1681
2099
|
});
|
|
1682
2100
|
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2103
|
+
*
|
|
2104
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2105
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2106
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2107
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2108
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2109
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2110
|
+
*
|
|
2111
|
+
* @remarks
|
|
2112
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2113
|
+
* with the updated attribute information.
|
|
2114
|
+
*/
|
|
1683
2115
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1684
2116
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2117
|
+
// Send the message to all connected clients
|
|
1685
2118
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1686
2119
|
if (client.readyState === WebSocket.OPEN) {
|
|
1687
2120
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1688
2121
|
}
|
|
1689
2122
|
});
|
|
1690
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Sends a message to all connected clients.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {number} id - The message id.
|
|
2128
|
+
* @param {string} method - The message method.
|
|
2129
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2130
|
+
*/
|
|
1691
2131
|
wssBroadcastMessage(id, method, params) {
|
|
1692
2132
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2133
|
+
// Send the message to all connected clients
|
|
1693
2134
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1694
2135
|
if (client.readyState === WebSocket.OPEN) {
|
|
1695
2136
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1697,3 +2138,4 @@ export class Frontend extends EventEmitter {
|
|
|
1697
2138
|
});
|
|
1698
2139
|
}
|
|
1699
2140
|
}
|
|
2141
|
+
//# sourceMappingURL=frontend.js.map
|