matterbridge 3.1.3-dev-20250714-03e05f2 → 3.1.3
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/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 +303 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +423 -18
- 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 +784 -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 +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,30 +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, 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;
|
|
@@ -38,7 +134,7 @@ export class Frontend extends EventEmitter {
|
|
|
38
134
|
constructor(matterbridge) {
|
|
39
135
|
super();
|
|
40
136
|
this.matterbridge = matterbridge;
|
|
41
|
-
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 */ });
|
|
42
138
|
}
|
|
43
139
|
set logLevel(logLevel) {
|
|
44
140
|
this.log.logLevel = logLevel;
|
|
@@ -46,12 +142,41 @@ export class Frontend extends EventEmitter {
|
|
|
46
142
|
async start(port = 8283) {
|
|
47
143
|
this.port = port;
|
|
48
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
|
|
49
146
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
50
147
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
51
148
|
const upload = multer({ dest: uploadDir });
|
|
149
|
+
// Create the express app that serves the frontend
|
|
52
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
|
|
53
177
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
54
178
|
if (!hasParameter('ssl')) {
|
|
179
|
+
// Create an HTTP server and attach the express app
|
|
55
180
|
try {
|
|
56
181
|
this.httpServer = createServer(this.expressApp);
|
|
57
182
|
}
|
|
@@ -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}`);
|
|
@@ -91,6 +217,7 @@ export class Frontend extends EventEmitter {
|
|
|
91
217
|
});
|
|
92
218
|
}
|
|
93
219
|
else {
|
|
220
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
94
221
|
let cert;
|
|
95
222
|
try {
|
|
96
223
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -120,6 +247,7 @@ export class Frontend extends EventEmitter {
|
|
|
120
247
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
121
248
|
}
|
|
122
249
|
const serverOptions = { cert, key, ca };
|
|
250
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
123
251
|
try {
|
|
124
252
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
125
253
|
}
|
|
@@ -128,6 +256,7 @@ export class Frontend extends EventEmitter {
|
|
|
128
256
|
this.emit('server_error', error);
|
|
129
257
|
return;
|
|
130
258
|
}
|
|
259
|
+
// Listen on the specified port
|
|
131
260
|
if (hasParameter('ingress')) {
|
|
132
261
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
133
262
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -160,16 +289,18 @@ export class Frontend extends EventEmitter {
|
|
|
160
289
|
}
|
|
161
290
|
if (this.initializeError)
|
|
162
291
|
return;
|
|
292
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
163
293
|
const wssPort = this.port;
|
|
164
294
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
165
295
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
166
296
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
167
297
|
const clientIp = request.socket.remoteAddress;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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 */;
|
|
173
304
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
174
305
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
175
306
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -204,6 +335,7 @@ export class Frontend extends EventEmitter {
|
|
|
204
335
|
this.webSocketServer.on('error', (ws, error) => {
|
|
205
336
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
206
337
|
});
|
|
338
|
+
// Subscribe to cli events
|
|
207
339
|
cliEmitter.removeAllListeners();
|
|
208
340
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
209
341
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -214,6 +346,8 @@ export class Frontend extends EventEmitter {
|
|
|
214
346
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
215
347
|
this.wssSendCpuUpdate(cpuUsage);
|
|
216
348
|
});
|
|
349
|
+
// Endpoint to validate login code
|
|
350
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
217
351
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
218
352
|
const { password } = req.body;
|
|
219
353
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -232,23 +366,27 @@ export class Frontend extends EventEmitter {
|
|
|
232
366
|
this.log.warn('/api/login error wrong password');
|
|
233
367
|
res.json({ valid: false });
|
|
234
368
|
}
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
235
370
|
}
|
|
236
371
|
catch (error) {
|
|
237
372
|
this.log.error('/api/login error getting password');
|
|
238
373
|
res.json({ valid: false });
|
|
239
374
|
}
|
|
240
375
|
});
|
|
376
|
+
// Endpoint to provide health check for docker
|
|
241
377
|
this.expressApp.get('/health', (req, res) => {
|
|
242
378
|
this.log.debug('Express received /health');
|
|
243
379
|
const healthStatus = {
|
|
244
|
-
status: 'ok',
|
|
245
|
-
uptime: process.uptime(),
|
|
246
|
-
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
|
|
247
383
|
};
|
|
248
384
|
res.status(200).json(healthStatus);
|
|
249
385
|
});
|
|
386
|
+
// Endpoint to provide memory usage details
|
|
250
387
|
this.expressApp.get('/memory', async (req, res) => {
|
|
251
388
|
this.log.debug('Express received /memory');
|
|
389
|
+
// Memory usage from process
|
|
252
390
|
const memoryUsageRaw = process.memoryUsage();
|
|
253
391
|
const memoryUsage = {
|
|
254
392
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -257,10 +395,13 @@ export class Frontend extends EventEmitter {
|
|
|
257
395
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
258
396
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
259
397
|
};
|
|
398
|
+
// V8 heap statistics
|
|
260
399
|
const { default: v8 } = await import('node:v8');
|
|
261
400
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
262
401
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
402
|
+
// Format heapStats
|
|
263
403
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
404
|
+
// Format heapSpaces
|
|
264
405
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
265
406
|
...space,
|
|
266
407
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -278,19 +419,23 @@ export class Frontend extends EventEmitter {
|
|
|
278
419
|
};
|
|
279
420
|
res.status(200).json(memoryReport);
|
|
280
421
|
});
|
|
422
|
+
// Endpoint to provide settings
|
|
281
423
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
282
424
|
this.log.debug('The frontend sent /api/settings');
|
|
283
425
|
res.json(await this.getApiSettings());
|
|
284
426
|
});
|
|
427
|
+
// Endpoint to provide plugins
|
|
285
428
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
286
429
|
this.log.debug('The frontend sent /api/plugins');
|
|
287
430
|
res.json(this.getBaseRegisteredPlugins());
|
|
288
431
|
});
|
|
432
|
+
// Endpoint to provide devices
|
|
289
433
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
290
434
|
this.log.debug('The frontend sent /api/devices');
|
|
291
435
|
const devices = await this.getDevices();
|
|
292
436
|
res.json(devices);
|
|
293
437
|
});
|
|
438
|
+
// Endpoint to view the matterbridge log
|
|
294
439
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
295
440
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
296
441
|
try {
|
|
@@ -303,6 +448,7 @@ export class Frontend extends EventEmitter {
|
|
|
303
448
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
304
449
|
}
|
|
305
450
|
});
|
|
451
|
+
// Endpoint to view the matter.js log
|
|
306
452
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
307
453
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
308
454
|
try {
|
|
@@ -315,6 +461,7 @@ export class Frontend extends EventEmitter {
|
|
|
315
461
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
316
462
|
}
|
|
317
463
|
});
|
|
464
|
+
// Endpoint to view the shelly log
|
|
318
465
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
319
466
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
320
467
|
try {
|
|
@@ -327,9 +474,11 @@ export class Frontend extends EventEmitter {
|
|
|
327
474
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
328
475
|
}
|
|
329
476
|
});
|
|
477
|
+
// Endpoint to download the matterbridge log
|
|
330
478
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
331
479
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
332
480
|
try {
|
|
481
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
333
482
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
|
|
334
483
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
|
|
335
484
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
|
|
@@ -340,15 +489,18 @@ export class Frontend extends EventEmitter {
|
|
|
340
489
|
}
|
|
341
490
|
res.type('text/plain');
|
|
342
491
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
492
|
+
/* istanbul ignore if */
|
|
343
493
|
if (error) {
|
|
344
494
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
345
495
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
346
496
|
}
|
|
347
497
|
});
|
|
348
498
|
});
|
|
499
|
+
// Endpoint to download the matter log
|
|
349
500
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
350
501
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
351
502
|
try {
|
|
503
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
352
504
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
353
505
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
354
506
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -359,15 +511,18 @@ export class Frontend extends EventEmitter {
|
|
|
359
511
|
}
|
|
360
512
|
res.type('text/plain');
|
|
361
513
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
514
|
+
/* istanbul ignore if */
|
|
362
515
|
if (error) {
|
|
363
516
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
364
517
|
res.status(500).send('Error downloading the matter log file');
|
|
365
518
|
}
|
|
366
519
|
});
|
|
367
520
|
});
|
|
521
|
+
// Endpoint to download the shelly log
|
|
368
522
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
369
523
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
370
524
|
try {
|
|
525
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
371
526
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
372
527
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
373
528
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -378,74 +533,90 @@ export class Frontend extends EventEmitter {
|
|
|
378
533
|
}
|
|
379
534
|
res.type('text/plain');
|
|
380
535
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
536
|
+
/* istanbul ignore if */
|
|
381
537
|
if (error) {
|
|
382
538
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
383
539
|
res.status(500).send('Error downloading Shelly system log file');
|
|
384
540
|
}
|
|
385
541
|
});
|
|
386
542
|
});
|
|
543
|
+
// Endpoint to download the matterbridge storage directory
|
|
387
544
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
388
545
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
389
546
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
390
547
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
548
|
+
/* istanbul ignore if */
|
|
391
549
|
if (error) {
|
|
392
550
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
393
551
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
394
552
|
}
|
|
395
553
|
});
|
|
396
554
|
});
|
|
555
|
+
// Endpoint to download the matter storage file
|
|
397
556
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
398
557
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
399
558
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
400
559
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
560
|
+
/* istanbul ignore if */
|
|
401
561
|
if (error) {
|
|
402
562
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
403
563
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
404
564
|
}
|
|
405
565
|
});
|
|
406
566
|
});
|
|
567
|
+
// Endpoint to download the matterbridge plugin directory
|
|
407
568
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
408
569
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
409
570
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
410
571
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
572
|
+
/* istanbul ignore if */
|
|
411
573
|
if (error) {
|
|
412
574
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
413
575
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
414
576
|
}
|
|
415
577
|
});
|
|
416
578
|
});
|
|
579
|
+
// Endpoint to download the matterbridge plugin config files
|
|
417
580
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
418
581
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
419
582
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
420
583
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
584
|
+
/* istanbul ignore if */
|
|
421
585
|
if (error) {
|
|
422
586
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
423
587
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
424
588
|
}
|
|
425
589
|
});
|
|
426
590
|
});
|
|
591
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
427
592
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
428
593
|
this.log.debug('The frontend sent /api/download-backup');
|
|
429
594
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
595
|
+
/* istanbul ignore if */
|
|
430
596
|
if (error) {
|
|
431
597
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
432
598
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
433
599
|
}
|
|
434
600
|
});
|
|
435
601
|
});
|
|
602
|
+
// Endpoint to upload a package
|
|
436
603
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
437
604
|
const { filename } = req.body;
|
|
438
605
|
const file = req.file;
|
|
606
|
+
/* istanbul ignore if */
|
|
439
607
|
if (!file || !filename) {
|
|
440
608
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
441
609
|
res.status(400).send('Invalid request: file and filename are required');
|
|
442
610
|
return;
|
|
443
611
|
}
|
|
444
612
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
613
|
+
// Define the path where the plugin file will be saved
|
|
445
614
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
446
615
|
try {
|
|
616
|
+
// Move the uploaded file to the specified path
|
|
447
617
|
await fs.rename(file.path, filePath);
|
|
448
618
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
619
|
+
// Install the plugin package
|
|
449
620
|
if (filename.endsWith('.tgz')) {
|
|
450
621
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
451
622
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -465,6 +636,7 @@ export class Frontend extends EventEmitter {
|
|
|
465
636
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
466
637
|
}
|
|
467
638
|
});
|
|
639
|
+
// Fallback for routing (must be the last route)
|
|
468
640
|
this.expressApp.use((req, res) => {
|
|
469
641
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
470
642
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -473,13 +645,16 @@ export class Frontend extends EventEmitter {
|
|
|
473
645
|
}
|
|
474
646
|
async stop() {
|
|
475
647
|
this.log.debug('Stopping the frontend...');
|
|
648
|
+
// Remove listeners from the express app
|
|
476
649
|
if (this.expressApp) {
|
|
477
650
|
this.expressApp.removeAllListeners();
|
|
478
651
|
this.expressApp = undefined;
|
|
479
652
|
this.log.debug('Frontend app closed successfully');
|
|
480
653
|
}
|
|
654
|
+
// Close the WebSocket server
|
|
481
655
|
if (this.webSocketServer) {
|
|
482
656
|
this.log.debug('Closing WebSocket server...');
|
|
657
|
+
// Close all active connections
|
|
483
658
|
this.webSocketServer.clients.forEach((client) => {
|
|
484
659
|
if (client.readyState === WebSocket.OPEN) {
|
|
485
660
|
client.close();
|
|
@@ -499,6 +674,7 @@ export class Frontend extends EventEmitter {
|
|
|
499
674
|
this.webSocketServer.removeAllListeners();
|
|
500
675
|
this.webSocketServer = undefined;
|
|
501
676
|
}
|
|
677
|
+
// Close the http server
|
|
502
678
|
if (this.httpServer) {
|
|
503
679
|
this.log.debug('Closing http server...');
|
|
504
680
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -516,6 +692,7 @@ export class Frontend extends EventEmitter {
|
|
|
516
692
|
this.httpServer = undefined;
|
|
517
693
|
this.log.debug('Frontend http server closed successfully');
|
|
518
694
|
}
|
|
695
|
+
// Close the https server
|
|
519
696
|
if (this.httpsServer) {
|
|
520
697
|
this.log.debug('Closing https server...');
|
|
521
698
|
await withTimeout(new Promise((resolve) => {
|
|
@@ -535,6 +712,7 @@ export class Frontend extends EventEmitter {
|
|
|
535
712
|
}
|
|
536
713
|
this.log.debug('Frontend stopped successfully');
|
|
537
714
|
}
|
|
715
|
+
// Function to format bytes to KB, MB, or GB
|
|
538
716
|
formatMemoryUsage = (bytes) => {
|
|
539
717
|
if (bytes >= 1024 ** 3) {
|
|
540
718
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -546,6 +724,7 @@ export class Frontend extends EventEmitter {
|
|
|
546
724
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
547
725
|
}
|
|
548
726
|
};
|
|
727
|
+
// Function to format system uptime with only the most significant unit
|
|
549
728
|
formatOsUpTime = (seconds) => {
|
|
550
729
|
if (seconds >= 86400) {
|
|
551
730
|
const days = Math.floor(seconds / 86400);
|
|
@@ -561,7 +740,13 @@ export class Frontend extends EventEmitter {
|
|
|
561
740
|
}
|
|
562
741
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
563
742
|
};
|
|
743
|
+
/**
|
|
744
|
+
* Retrieves the api settings data.
|
|
745
|
+
*
|
|
746
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
747
|
+
*/
|
|
564
748
|
async getApiSettings() {
|
|
749
|
+
// Update the system information
|
|
565
750
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
566
751
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
567
752
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -570,6 +755,7 @@ export class Frontend extends EventEmitter {
|
|
|
570
755
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
571
756
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
572
757
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
758
|
+
// Update the matterbridge information
|
|
573
759
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
574
760
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
575
761
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -581,6 +767,7 @@ export class Frontend extends EventEmitter {
|
|
|
581
767
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
582
768
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
583
769
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
770
|
+
// Update the matterbridge information in bridge mode
|
|
584
771
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode) {
|
|
585
772
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
586
773
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -590,6 +777,12 @@ export class Frontend extends EventEmitter {
|
|
|
590
777
|
}
|
|
591
778
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
592
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* Retrieves the reachable attribute.
|
|
782
|
+
*
|
|
783
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
784
|
+
* @returns {boolean} The reachable attribute.
|
|
785
|
+
*/
|
|
593
786
|
getReachability(device) {
|
|
594
787
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
595
788
|
return false;
|
|
@@ -601,6 +794,12 @@ export class Frontend extends EventEmitter {
|
|
|
601
794
|
return true;
|
|
602
795
|
return false;
|
|
603
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Retrieves the power source attribute.
|
|
799
|
+
*
|
|
800
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
801
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
802
|
+
*/
|
|
604
803
|
getPowerSource(endpoint) {
|
|
605
804
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
606
805
|
return undefined;
|
|
@@ -616,13 +815,21 @@ export class Frontend extends EventEmitter {
|
|
|
616
815
|
}
|
|
617
816
|
return;
|
|
618
817
|
};
|
|
818
|
+
// Root endpoint
|
|
619
819
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
620
820
|
return powerSource(endpoint);
|
|
821
|
+
// Child endpoints
|
|
621
822
|
for (const child of endpoint.getChildEndpoints()) {
|
|
622
823
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
623
824
|
return powerSource(child);
|
|
624
825
|
}
|
|
625
826
|
}
|
|
827
|
+
/**
|
|
828
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
829
|
+
*
|
|
830
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
831
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
832
|
+
*/
|
|
626
833
|
getMatterDataFromDevice(device) {
|
|
627
834
|
if (device.mode === 'server' && device.serverNode) {
|
|
628
835
|
return {
|
|
@@ -634,6 +841,13 @@ export class Frontend extends EventEmitter {
|
|
|
634
841
|
};
|
|
635
842
|
}
|
|
636
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Retrieves the cluster text description from a given device.
|
|
846
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
847
|
+
*
|
|
848
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
849
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
850
|
+
*/
|
|
637
851
|
getClusterTextFromDevice(device) {
|
|
638
852
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
639
853
|
return '';
|
|
@@ -658,6 +872,7 @@ export class Frontend extends EventEmitter {
|
|
|
658
872
|
let attributes = '';
|
|
659
873
|
let supportedModes = [];
|
|
660
874
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
875
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
661
876
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
662
877
|
return;
|
|
663
878
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -747,11 +962,17 @@ export class Frontend extends EventEmitter {
|
|
|
747
962
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
748
963
|
attributes += `${getUserLabel(device)} `;
|
|
749
964
|
});
|
|
965
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
750
966
|
return attributes.trimStart().trimEnd();
|
|
751
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
970
|
+
*
|
|
971
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
972
|
+
*/
|
|
752
973
|
getBaseRegisteredPlugins() {
|
|
753
974
|
if (this.matterbridge.hasCleanupStarted)
|
|
754
|
-
return [];
|
|
975
|
+
return []; // Skip if cleanup has started
|
|
755
976
|
const baseRegisteredPlugins = [];
|
|
756
977
|
for (const plugin of this.matterbridge.plugins) {
|
|
757
978
|
baseRegisteredPlugins.push({
|
|
@@ -780,6 +1001,7 @@ export class Frontend extends EventEmitter {
|
|
|
780
1001
|
schemaJson: plugin.schemaJson,
|
|
781
1002
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
782
1003
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1004
|
+
// Childbridge mode specific data
|
|
783
1005
|
paired: plugin.serverNode?.state.commissioning.commissioned,
|
|
784
1006
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.qrPairingCode,
|
|
785
1007
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode?.state.commissioning.pairingCodes.manualPairingCode,
|
|
@@ -789,13 +1011,21 @@ export class Frontend extends EventEmitter {
|
|
|
789
1011
|
}
|
|
790
1012
|
return baseRegisteredPlugins;
|
|
791
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Retrieves the devices from Matterbridge.
|
|
1016
|
+
*
|
|
1017
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1018
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1019
|
+
*/
|
|
792
1020
|
async getDevices(pluginName) {
|
|
793
1021
|
if (this.matterbridge.hasCleanupStarted)
|
|
794
|
-
return [];
|
|
1022
|
+
return []; // Skip if cleanup has started
|
|
795
1023
|
const devices = [];
|
|
796
1024
|
for (const device of this.matterbridge.devices.array()) {
|
|
1025
|
+
// Filter by pluginName if provided
|
|
797
1026
|
if (pluginName && pluginName !== device.plugin)
|
|
798
1027
|
continue;
|
|
1028
|
+
// Check if the device has the required properties
|
|
799
1029
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
800
1030
|
continue;
|
|
801
1031
|
devices.push({
|
|
@@ -815,22 +1045,37 @@ export class Frontend extends EventEmitter {
|
|
|
815
1045
|
}
|
|
816
1046
|
return devices;
|
|
817
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1050
|
+
*
|
|
1051
|
+
* Response for /api/clusters
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1054
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1055
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1056
|
+
*/
|
|
818
1057
|
getClusters(pluginName, endpointNumber) {
|
|
819
1058
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
820
1059
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
821
1060
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
822
1061
|
return;
|
|
823
1062
|
}
|
|
1063
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1064
|
+
// Get the device types from the main endpoint
|
|
824
1065
|
const deviceTypes = [];
|
|
825
1066
|
const clusters = [];
|
|
826
1067
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
827
1068
|
deviceTypes.push(d.deviceType);
|
|
828
1069
|
});
|
|
1070
|
+
// Get the clusters from the main endpoint
|
|
829
1071
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
830
1072
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
831
1073
|
return;
|
|
832
1074
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
833
1075
|
return;
|
|
1076
|
+
// console.log(
|
|
1077
|
+
// `${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}`,
|
|
1078
|
+
// );
|
|
834
1079
|
clusters.push({
|
|
835
1080
|
endpoint: endpoint.number.toString(),
|
|
836
1081
|
id: 'main',
|
|
@@ -843,12 +1088,18 @@ export class Frontend extends EventEmitter {
|
|
|
843
1088
|
attributeLocalValue: attributeValue,
|
|
844
1089
|
});
|
|
845
1090
|
});
|
|
1091
|
+
// Get the child endpoints
|
|
846
1092
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1093
|
+
// if (childEndpoints.length === 0) {
|
|
1094
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1095
|
+
// }
|
|
847
1096
|
childEndpoints.forEach((childEndpoint) => {
|
|
848
1097
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
849
1098
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
850
1099
|
return;
|
|
851
1100
|
}
|
|
1101
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1102
|
+
// Get the device types of the child endpoint
|
|
852
1103
|
const deviceTypes = [];
|
|
853
1104
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
854
1105
|
deviceTypes.push(d.deviceType);
|
|
@@ -858,9 +1109,12 @@ export class Frontend extends EventEmitter {
|
|
|
858
1109
|
return;
|
|
859
1110
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
860
1111
|
return;
|
|
1112
|
+
// console.log(
|
|
1113
|
+
// `${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}`,
|
|
1114
|
+
// );
|
|
861
1115
|
clusters.push({
|
|
862
1116
|
endpoint: childEndpoint.number.toString(),
|
|
863
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1117
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
864
1118
|
deviceTypes,
|
|
865
1119
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
866
1120
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -873,6 +1127,13 @@ export class Frontend extends EventEmitter {
|
|
|
873
1127
|
});
|
|
874
1128
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
875
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1132
|
+
*
|
|
1133
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1134
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1135
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1136
|
+
*/
|
|
876
1137
|
async wsMessageHandler(client, message) {
|
|
877
1138
|
let data;
|
|
878
1139
|
try {
|
|
@@ -919,32 +1180,41 @@ export class Frontend extends EventEmitter {
|
|
|
919
1180
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
920
1181
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
921
1182
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1183
|
+
// The install comes from InstallPlugins
|
|
922
1184
|
this.matterbridge.plugins
|
|
923
1185
|
.add(packageName)
|
|
924
1186
|
.then((plugin) => {
|
|
925
1187
|
if (plugin) {
|
|
1188
|
+
// The plugin is not registered
|
|
926
1189
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
927
1190
|
this.matterbridge.plugins
|
|
928
1191
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1192
|
+
// eslint-disable-next-line promise/no-nesting
|
|
929
1193
|
.then(() => {
|
|
930
1194
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
931
1195
|
this.wssSendRefreshRequired('plugins');
|
|
932
1196
|
return;
|
|
933
1197
|
})
|
|
1198
|
+
// eslint-disable-next-line promise/no-nesting
|
|
934
1199
|
.catch((_error) => {
|
|
1200
|
+
//
|
|
935
1201
|
});
|
|
936
1202
|
}
|
|
937
1203
|
else {
|
|
1204
|
+
// The plugin is already registered
|
|
938
1205
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
939
1206
|
this.wssSendRefreshRequired('plugins');
|
|
940
1207
|
this.wssSendRestartRequired();
|
|
941
1208
|
}
|
|
942
1209
|
return;
|
|
943
1210
|
})
|
|
1211
|
+
// eslint-disable-next-line promise/no-nesting
|
|
944
1212
|
.catch((_error) => {
|
|
1213
|
+
//
|
|
945
1214
|
});
|
|
946
1215
|
}
|
|
947
1216
|
else {
|
|
1217
|
+
// The package is matterbridge
|
|
948
1218
|
if (this.matterbridge.restartMode !== '') {
|
|
949
1219
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
950
1220
|
this.matterbridge.shutdownProcess();
|
|
@@ -967,6 +1237,7 @@ export class Frontend extends EventEmitter {
|
|
|
967
1237
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
968
1238
|
return;
|
|
969
1239
|
}
|
|
1240
|
+
// The package is a plugin
|
|
970
1241
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
971
1242
|
if (plugin) {
|
|
972
1243
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -975,6 +1246,7 @@ export class Frontend extends EventEmitter {
|
|
|
975
1246
|
this.wssSendRefreshRequired('plugins');
|
|
976
1247
|
this.wssSendRefreshRequired('devices');
|
|
977
1248
|
}
|
|
1249
|
+
// Uninstall the package
|
|
978
1250
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
979
1251
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
980
1252
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1015,6 +1287,7 @@ export class Frontend extends EventEmitter {
|
|
|
1015
1287
|
return;
|
|
1016
1288
|
})
|
|
1017
1289
|
.catch((_error) => {
|
|
1290
|
+
//
|
|
1018
1291
|
});
|
|
1019
1292
|
}
|
|
1020
1293
|
else {
|
|
@@ -1061,6 +1334,7 @@ export class Frontend extends EventEmitter {
|
|
|
1061
1334
|
return;
|
|
1062
1335
|
})
|
|
1063
1336
|
.catch((_error) => {
|
|
1337
|
+
//
|
|
1064
1338
|
});
|
|
1065
1339
|
}
|
|
1066
1340
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1171,6 +1445,8 @@ export class Frontend extends EventEmitter {
|
|
|
1171
1445
|
else if (data.method === '/api/advertise') {
|
|
1172
1446
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1173
1447
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1448
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1449
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1174
1450
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1175
1451
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1176
1452
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1293,22 +1569,22 @@ export class Frontend extends EventEmitter {
|
|
|
1293
1569
|
if (isValidString(data.params.value, 4)) {
|
|
1294
1570
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1295
1571
|
if (data.params.value === 'Debug') {
|
|
1296
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1572
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1297
1573
|
}
|
|
1298
1574
|
else if (data.params.value === 'Info') {
|
|
1299
|
-
await this.matterbridge.setLogLevel("info");
|
|
1575
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1300
1576
|
}
|
|
1301
1577
|
else if (data.params.value === 'Notice') {
|
|
1302
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1578
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1303
1579
|
}
|
|
1304
1580
|
else if (data.params.value === 'Warn') {
|
|
1305
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1581
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1306
1582
|
}
|
|
1307
1583
|
else if (data.params.value === 'Error') {
|
|
1308
|
-
await this.matterbridge.setLogLevel("error");
|
|
1584
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1309
1585
|
}
|
|
1310
1586
|
else if (data.params.value === 'Fatal') {
|
|
1311
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1587
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1312
1588
|
}
|
|
1313
1589
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1314
1590
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1319,6 +1595,7 @@ export class Frontend extends EventEmitter {
|
|
|
1319
1595
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1320
1596
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1321
1597
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1598
|
+
// Create the file logger for matterbridge
|
|
1322
1599
|
if (data.params.value)
|
|
1323
1600
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1324
1601
|
else
|
|
@@ -1365,6 +1642,7 @@ export class Frontend extends EventEmitter {
|
|
|
1365
1642
|
});
|
|
1366
1643
|
}
|
|
1367
1644
|
catch (error) {
|
|
1645
|
+
/* istanbul ignore next */
|
|
1368
1646
|
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
1647
|
}
|
|
1370
1648
|
}
|
|
@@ -1373,6 +1651,7 @@ export class Frontend extends EventEmitter {
|
|
|
1373
1651
|
Logger.removeLogger('matterfilelogger');
|
|
1374
1652
|
}
|
|
1375
1653
|
catch (error) {
|
|
1654
|
+
/* istanbul ignore next */
|
|
1376
1655
|
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
1656
|
}
|
|
1378
1657
|
}
|
|
@@ -1485,15 +1764,19 @@ export class Frontend extends EventEmitter {
|
|
|
1485
1764
|
return;
|
|
1486
1765
|
}
|
|
1487
1766
|
const config = plugin.configJson;
|
|
1767
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1488
1768
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1769
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1489
1770
|
if (select === 'serial')
|
|
1490
1771
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1491
1772
|
if (select === 'name')
|
|
1492
1773
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1493
1774
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1775
|
+
// Remove postfix from the serial if it exists
|
|
1494
1776
|
if (config.postfix) {
|
|
1495
1777
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1496
1778
|
}
|
|
1779
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1497
1780
|
if (isValidArray(config.whiteList, 1)) {
|
|
1498
1781
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1499
1782
|
config.whiteList.push(data.params.serial);
|
|
@@ -1502,6 +1785,7 @@ export class Frontend extends EventEmitter {
|
|
|
1502
1785
|
config.whiteList.push(data.params.name);
|
|
1503
1786
|
}
|
|
1504
1787
|
}
|
|
1788
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1505
1789
|
if (isValidArray(config.blackList, 1)) {
|
|
1506
1790
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1507
1791
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1532,7 +1816,9 @@ export class Frontend extends EventEmitter {
|
|
|
1532
1816
|
return;
|
|
1533
1817
|
}
|
|
1534
1818
|
const config = plugin.configJson;
|
|
1819
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1535
1820
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1821
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1536
1822
|
if (select === 'serial')
|
|
1537
1823
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1538
1824
|
if (select === 'name')
|
|
@@ -1541,6 +1827,7 @@ export class Frontend extends EventEmitter {
|
|
|
1541
1827
|
if (config.postfix) {
|
|
1542
1828
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1543
1829
|
}
|
|
1830
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1544
1831
|
if (isValidArray(config.whiteList, 1)) {
|
|
1545
1832
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1546
1833
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1549,6 +1836,7 @@ export class Frontend extends EventEmitter {
|
|
|
1549
1836
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1550
1837
|
}
|
|
1551
1838
|
}
|
|
1839
|
+
// Add the serial to the blackList
|
|
1552
1840
|
if (isValidArray(config.blackList)) {
|
|
1553
1841
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1554
1842
|
config.blackList.push(data.params.serial);
|
|
@@ -1582,114 +1870,230 @@ export class Frontend extends EventEmitter {
|
|
|
1582
1870
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1583
1871
|
}
|
|
1584
1872
|
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1875
|
+
*
|
|
1876
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1877
|
+
* @param {string} time - The time string of the message
|
|
1878
|
+
* @param {string} name - The logger name of the message
|
|
1879
|
+
* @param {string} message - The content of the message.
|
|
1880
|
+
*
|
|
1881
|
+
* @remarks
|
|
1882
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1883
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1884
|
+
* The function sends the message to all connected clients.
|
|
1885
|
+
*/
|
|
1585
1886
|
wssSendMessage(level, time, name, message) {
|
|
1586
1887
|
if (!level || !time || !name || !message)
|
|
1587
1888
|
return;
|
|
1889
|
+
// Remove ANSI escape codes from the message
|
|
1890
|
+
// eslint-disable-next-line no-control-regex
|
|
1588
1891
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1892
|
+
// Remove leading asterisks from the message
|
|
1589
1893
|
message = message.replace(/^\*+/, '');
|
|
1894
|
+
// Replace all occurrences of \t and \n
|
|
1590
1895
|
message = message.replace(/[\t\n]/g, '');
|
|
1896
|
+
// Remove non-printable characters
|
|
1897
|
+
// eslint-disable-next-line no-control-regex
|
|
1591
1898
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1899
|
+
// Replace all occurrences of \" with "
|
|
1592
1900
|
message = message.replace(/\\"/g, '"');
|
|
1901
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1593
1902
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1903
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1594
1904
|
const maxContinuousLength = 100;
|
|
1595
1905
|
const keepStartLength = 20;
|
|
1596
1906
|
const keepEndLength = 20;
|
|
1907
|
+
// Split the message into words
|
|
1597
1908
|
message = message
|
|
1598
1909
|
.split(' ')
|
|
1599
1910
|
.map((word) => {
|
|
1911
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1600
1912
|
if (word.length > maxContinuousLength) {
|
|
1601
1913
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1602
1914
|
}
|
|
1603
1915
|
return word;
|
|
1604
1916
|
})
|
|
1605
1917
|
.join(' ');
|
|
1918
|
+
// Send the message to all connected clients
|
|
1606
1919
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1607
1920
|
if (client.readyState === WebSocket.OPEN) {
|
|
1608
1921
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1609
1922
|
}
|
|
1610
1923
|
});
|
|
1611
1924
|
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1927
|
+
*
|
|
1928
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1929
|
+
* possible values:
|
|
1930
|
+
* - 'matterbridgeLatestVersion'
|
|
1931
|
+
* - 'matterbridgeAdvertise'
|
|
1932
|
+
* - 'online'
|
|
1933
|
+
* - 'offline'
|
|
1934
|
+
* - 'reachability'
|
|
1935
|
+
* - 'settings'
|
|
1936
|
+
* - 'plugins'
|
|
1937
|
+
* - 'pluginsRestart'
|
|
1938
|
+
* - 'devices'
|
|
1939
|
+
* - 'fabrics'
|
|
1940
|
+
* - 'sessions'
|
|
1941
|
+
*/
|
|
1612
1942
|
wssSendRefreshRequired(changed = null) {
|
|
1613
1943
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1944
|
+
// Send the message to all connected clients
|
|
1614
1945
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1615
1946
|
if (client.readyState === WebSocket.OPEN) {
|
|
1616
1947
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1617
1948
|
}
|
|
1618
1949
|
});
|
|
1619
1950
|
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1953
|
+
*
|
|
1954
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1955
|
+
*/
|
|
1620
1956
|
wssSendRestartRequired(snackbar = true) {
|
|
1621
1957
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1622
1958
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1623
1959
|
if (snackbar === true)
|
|
1624
1960
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1961
|
+
// Send the message to all connected clients
|
|
1625
1962
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1626
1963
|
if (client.readyState === WebSocket.OPEN) {
|
|
1627
1964
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1628
1965
|
}
|
|
1629
1966
|
});
|
|
1630
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1970
|
+
*
|
|
1971
|
+
*/
|
|
1631
1972
|
wssSendUpdateRequired() {
|
|
1632
1973
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1633
1974
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1975
|
+
// Send the message to all connected clients
|
|
1634
1976
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1635
1977
|
if (client.readyState === WebSocket.OPEN) {
|
|
1636
1978
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1637
1979
|
}
|
|
1638
1980
|
});
|
|
1639
1981
|
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Sends a cpu update message to all connected clients.
|
|
1984
|
+
*
|
|
1985
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
1986
|
+
*/
|
|
1640
1987
|
wssSendCpuUpdate(cpuUsage) {
|
|
1641
1988
|
if (hasParameter('debug'))
|
|
1642
1989
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1990
|
+
// Send the message to all connected clients
|
|
1643
1991
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1644
1992
|
if (client.readyState === WebSocket.OPEN) {
|
|
1645
1993
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1646
1994
|
}
|
|
1647
1995
|
});
|
|
1648
1996
|
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Sends a memory update message to all connected clients.
|
|
1999
|
+
*
|
|
2000
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2001
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2002
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2003
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2004
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2005
|
+
* @param {string} external - The external memory in bytes.
|
|
2006
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2007
|
+
*/
|
|
1649
2008
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1650
2009
|
if (hasParameter('debug'))
|
|
1651
2010
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2011
|
+
// Send the message to all connected clients
|
|
1652
2012
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1653
2013
|
if (client.readyState === WebSocket.OPEN) {
|
|
1654
2014
|
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
2015
|
}
|
|
1656
2016
|
});
|
|
1657
2017
|
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Sends an uptime update message to all connected clients.
|
|
2020
|
+
*
|
|
2021
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2022
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2023
|
+
*/
|
|
1658
2024
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1659
2025
|
if (hasParameter('debug'))
|
|
1660
2026
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2027
|
+
// Send the message to all connected clients
|
|
1661
2028
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1662
2029
|
if (client.readyState === WebSocket.OPEN) {
|
|
1663
2030
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1664
2031
|
}
|
|
1665
2032
|
});
|
|
1666
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Sends an open snackbar message to all connected clients.
|
|
2036
|
+
*
|
|
2037
|
+
* @param {string} message - The message to send.
|
|
2038
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2039
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2040
|
+
*/
|
|
1667
2041
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1668
2042
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2043
|
+
// Send the message to all connected clients
|
|
1669
2044
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1670
2045
|
if (client.readyState === WebSocket.OPEN) {
|
|
1671
2046
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1672
2047
|
}
|
|
1673
2048
|
});
|
|
1674
2049
|
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Sends a close snackbar message to all connected clients.
|
|
2052
|
+
*
|
|
2053
|
+
* @param {string} message - The message to send.
|
|
2054
|
+
*/
|
|
1675
2055
|
wssSendCloseSnackbarMessage(message) {
|
|
1676
2056
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2057
|
+
// Send the message to all connected clients
|
|
1677
2058
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1678
2059
|
if (client.readyState === WebSocket.OPEN) {
|
|
1679
2060
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1680
2061
|
}
|
|
1681
2062
|
});
|
|
1682
2063
|
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2066
|
+
*
|
|
2067
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2068
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2069
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2070
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2071
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2072
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2073
|
+
*
|
|
2074
|
+
* @remarks
|
|
2075
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2076
|
+
* with the updated attribute information.
|
|
2077
|
+
*/
|
|
1683
2078
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1684
2079
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2080
|
+
// Send the message to all connected clients
|
|
1685
2081
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1686
2082
|
if (client.readyState === WebSocket.OPEN) {
|
|
1687
2083
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1688
2084
|
}
|
|
1689
2085
|
});
|
|
1690
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Sends a message to all connected clients.
|
|
2089
|
+
*
|
|
2090
|
+
* @param {number} id - The message id.
|
|
2091
|
+
* @param {string} method - The message method.
|
|
2092
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2093
|
+
*/
|
|
1691
2094
|
wssBroadcastMessage(id, method, params) {
|
|
1692
2095
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2096
|
+
// Send the message to all connected clients
|
|
1693
2097
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1694
2098
|
if (client.readyState === WebSocket.OPEN) {
|
|
1695
2099
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1697,3 +2101,4 @@ export class Frontend extends EventEmitter {
|
|
|
1697
2101
|
});
|
|
1698
2102
|
}
|
|
1699
2103
|
}
|
|
2104
|
+
//# sourceMappingURL=frontend.js.map
|