matterbridge 3.1.0-dev-20250627-94e7f6e → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -8
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +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/export.d.ts +5 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/evse.d.ts +72 -0
- package/dist/evse.d.ts.map +1 -0
- package/dist/evse.js +70 -9
- package/dist/evse.js.map +1 -0
- package/dist/frontend.d.ts +285 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +413 -16
- 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 +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -0
- package/dist/laundryWasher.d.ts +243 -0
- package/dist/laundryWasher.d.ts.map +1 -0
- package/dist/laundryWasher.js +92 -7
- package/dist/laundryWasher.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +450 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +809 -55
- 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 +1334 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +55 -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 +1173 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1023 -41
- 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 +184 -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/roboticVacuumCleaner.d.ts +104 -0
- package/dist/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/roboticVacuumCleaner.js +83 -6
- package/dist/roboticVacuumCleaner.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 +14 -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/dist/waterHeater.d.ts +106 -0
- package/dist/waterHeater.d.ts.map +1 -0
- package/dist/waterHeater.js +77 -2
- package/dist/waterHeater.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,29 +1,125 @@
|
|
|
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.0.2
|
|
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';
|
|
30
|
+
// Third-party modules
|
|
6
31
|
import express from 'express';
|
|
7
32
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
8
33
|
import multer from 'multer';
|
|
34
|
+
// AnsiLogger module
|
|
9
35
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from 'node-ansi-logger';
|
|
36
|
+
// @matter
|
|
10
37
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
11
38
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
39
|
+
// Matterbridge
|
|
12
40
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
|
|
13
41
|
import { plg } from './matterbridgeTypes.js';
|
|
14
42
|
import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
|
|
15
43
|
import spawn from './utils/spawn.js';
|
|
44
|
+
/**
|
|
45
|
+
* Websocket message ID for logging.
|
|
46
|
+
*
|
|
47
|
+
* @constant {number}
|
|
48
|
+
*/
|
|
16
49
|
export const WS_ID_LOG = 0;
|
|
50
|
+
/**
|
|
51
|
+
* Websocket message ID indicating a refresh is needed.
|
|
52
|
+
*
|
|
53
|
+
* @constant {number}
|
|
54
|
+
*/
|
|
17
55
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
56
|
+
/**
|
|
57
|
+
* Websocket message ID indicating a restart is needed.
|
|
58
|
+
*
|
|
59
|
+
* @constant {number}
|
|
60
|
+
*/
|
|
18
61
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
62
|
+
/**
|
|
63
|
+
* Websocket message ID indicating a cpu update.
|
|
64
|
+
*
|
|
65
|
+
* @constant {number}
|
|
66
|
+
*/
|
|
19
67
|
export const WS_ID_CPU_UPDATE = 3;
|
|
68
|
+
/**
|
|
69
|
+
* Websocket message ID indicating a memory update.
|
|
70
|
+
*
|
|
71
|
+
* @constant {number}
|
|
72
|
+
*/
|
|
20
73
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
74
|
+
/**
|
|
75
|
+
* Websocket message ID indicating an uptime update.
|
|
76
|
+
*
|
|
77
|
+
* @constant {number}
|
|
78
|
+
*/
|
|
21
79
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
80
|
+
/**
|
|
81
|
+
* Websocket message ID indicating a snackbar message.
|
|
82
|
+
*
|
|
83
|
+
* @constant {number}
|
|
84
|
+
*/
|
|
22
85
|
export const WS_ID_SNACKBAR = 6;
|
|
86
|
+
/**
|
|
87
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
88
|
+
*
|
|
89
|
+
* @constant {number}
|
|
90
|
+
*/
|
|
23
91
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
92
|
+
/**
|
|
93
|
+
* Websocket message ID indicating a state update.
|
|
94
|
+
*
|
|
95
|
+
* @constant {number}
|
|
96
|
+
*/
|
|
24
97
|
export const WS_ID_STATEUPDATE = 8;
|
|
98
|
+
/**
|
|
99
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
100
|
+
*
|
|
101
|
+
* @constant {number}
|
|
102
|
+
*/
|
|
25
103
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
104
|
+
/**
|
|
105
|
+
* Websocket message ID indicating a shelly system update.
|
|
106
|
+
* check:
|
|
107
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
108
|
+
* perform:
|
|
109
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
110
|
+
*
|
|
111
|
+
* @constant {number}
|
|
112
|
+
*/
|
|
26
113
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
114
|
+
/**
|
|
115
|
+
* Websocket message ID indicating a shelly main update.
|
|
116
|
+
* check:
|
|
117
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
118
|
+
* perform:
|
|
119
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
120
|
+
*
|
|
121
|
+
* @constant {number}
|
|
122
|
+
*/
|
|
27
123
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
28
124
|
export class Frontend {
|
|
29
125
|
matterbridge;
|
|
@@ -36,7 +132,7 @@ export class Frontend {
|
|
|
36
132
|
webSocketServer;
|
|
37
133
|
constructor(matterbridge) {
|
|
38
134
|
this.matterbridge = matterbridge;
|
|
39
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
135
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
40
136
|
}
|
|
41
137
|
set logLevel(logLevel) {
|
|
42
138
|
this.log.logLevel = logLevel;
|
|
@@ -44,13 +140,43 @@ export class Frontend {
|
|
|
44
140
|
async start(port = 8283) {
|
|
45
141
|
this.port = port;
|
|
46
142
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
143
|
+
// Initialize multer with the upload directory
|
|
47
144
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
48
145
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
49
146
|
const upload = multer({ dest: uploadDir });
|
|
147
|
+
// Create the express app that serves the frontend
|
|
50
148
|
this.expressApp = express();
|
|
149
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
150
|
+
/*
|
|
151
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
152
|
+
for (const method of methods) {
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
157
|
+
try {
|
|
158
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
159
|
+
return original(path, ...rest);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
*/
|
|
167
|
+
// Log all requests to the server for debugging
|
|
168
|
+
/*
|
|
169
|
+
this.expressApp.use((req, res, next) => {
|
|
170
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
171
|
+
next();
|
|
172
|
+
});
|
|
173
|
+
*/
|
|
174
|
+
// Serve static files from '/static' endpoint
|
|
51
175
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
52
176
|
if (!hasParameter('ssl')) {
|
|
177
|
+
// Create an HTTP server and attach the express app
|
|
53
178
|
this.httpServer = createServer(this.expressApp);
|
|
179
|
+
// Listen on the specified port
|
|
54
180
|
if (hasParameter('ingress')) {
|
|
55
181
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
56
182
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -64,6 +190,7 @@ export class Frontend {
|
|
|
64
190
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
65
191
|
});
|
|
66
192
|
}
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
194
|
this.httpServer.on('error', (error) => {
|
|
68
195
|
this.log.error(`Frontend http server error listening on ${this.port}`);
|
|
69
196
|
switch (error.code) {
|
|
@@ -79,6 +206,7 @@ export class Frontend {
|
|
|
79
206
|
});
|
|
80
207
|
}
|
|
81
208
|
else {
|
|
209
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
82
210
|
let cert;
|
|
83
211
|
try {
|
|
84
212
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -106,7 +234,9 @@ export class Frontend {
|
|
|
106
234
|
this.log.info(`CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
107
235
|
}
|
|
108
236
|
const serverOptions = { cert, key, ca };
|
|
237
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
109
238
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
239
|
+
// Listen on the specified port
|
|
110
240
|
if (hasParameter('ingress')) {
|
|
111
241
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
112
242
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -120,6 +250,7 @@ export class Frontend {
|
|
|
120
250
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.matterbridge.systemInformation.ipv6Address}]:${this.port}${UNDERLINEOFF}${rs}`);
|
|
121
251
|
});
|
|
122
252
|
}
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
254
|
this.httpsServer.on('error', (error) => {
|
|
124
255
|
this.log.error(`Frontend https server error listening on ${this.port}`);
|
|
125
256
|
switch (error.code) {
|
|
@@ -136,16 +267,18 @@ export class Frontend {
|
|
|
136
267
|
}
|
|
137
268
|
if (this.initializeError)
|
|
138
269
|
return;
|
|
270
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
139
271
|
const wssPort = this.port;
|
|
140
272
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
141
273
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
142
274
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
143
275
|
const clientIp = request.socket.remoteAddress;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
276
|
+
// Set the global logger callback for the WebSocketServer
|
|
277
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
278
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
279
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
280
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
281
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
149
282
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
150
283
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
151
284
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -179,6 +312,7 @@ export class Frontend {
|
|
|
179
312
|
this.webSocketServer.on('error', (ws, error) => {
|
|
180
313
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
181
314
|
});
|
|
315
|
+
// Subscribe to cli events
|
|
182
316
|
const { cliEmitter } = await import('./cli.js');
|
|
183
317
|
cliEmitter.removeAllListeners();
|
|
184
318
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
@@ -190,6 +324,8 @@ export class Frontend {
|
|
|
190
324
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
191
325
|
this.wssSendCpuUpdate(cpuUsage);
|
|
192
326
|
});
|
|
327
|
+
// Endpoint to validate login code
|
|
328
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
193
329
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
194
330
|
const { password } = req.body;
|
|
195
331
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -208,23 +344,27 @@ export class Frontend {
|
|
|
208
344
|
this.log.warn('/api/login error wrong password');
|
|
209
345
|
res.json({ valid: false });
|
|
210
346
|
}
|
|
347
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
211
348
|
}
|
|
212
349
|
catch (error) {
|
|
213
350
|
this.log.error('/api/login error getting password');
|
|
214
351
|
res.json({ valid: false });
|
|
215
352
|
}
|
|
216
353
|
});
|
|
354
|
+
// Endpoint to provide health check for docker
|
|
217
355
|
this.expressApp.get('/health', (req, res) => {
|
|
218
356
|
this.log.debug('Express received /health');
|
|
219
357
|
const healthStatus = {
|
|
220
|
-
status: 'ok',
|
|
221
|
-
uptime: process.uptime(),
|
|
222
|
-
timestamp: new Date().toISOString(),
|
|
358
|
+
status: 'ok', // Indicate service is healthy
|
|
359
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
360
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
223
361
|
};
|
|
224
362
|
res.status(200).json(healthStatus);
|
|
225
363
|
});
|
|
364
|
+
// Endpoint to provide memory usage details
|
|
226
365
|
this.expressApp.get('/memory', async (req, res) => {
|
|
227
366
|
this.log.debug('Express received /memory');
|
|
367
|
+
// Memory usage from process
|
|
228
368
|
const memoryUsageRaw = process.memoryUsage();
|
|
229
369
|
const memoryUsage = {
|
|
230
370
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -233,10 +373,13 @@ export class Frontend {
|
|
|
233
373
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
234
374
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
235
375
|
};
|
|
376
|
+
// V8 heap statistics
|
|
236
377
|
const { default: v8 } = await import('node:v8');
|
|
237
378
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
238
379
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
380
|
+
// Format heapStats
|
|
239
381
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
382
|
+
// Format heapSpaces
|
|
240
383
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
241
384
|
...space,
|
|
242
385
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -254,19 +397,23 @@ export class Frontend {
|
|
|
254
397
|
};
|
|
255
398
|
res.status(200).json(memoryReport);
|
|
256
399
|
});
|
|
400
|
+
// Endpoint to provide settings
|
|
257
401
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
258
402
|
this.log.debug('The frontend sent /api/settings');
|
|
259
403
|
res.json(await this.getApiSettings());
|
|
260
404
|
});
|
|
405
|
+
// Endpoint to provide plugins
|
|
261
406
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
262
407
|
this.log.debug('The frontend sent /api/plugins');
|
|
263
408
|
res.json(this.getBaseRegisteredPlugins());
|
|
264
409
|
});
|
|
410
|
+
// Endpoint to provide devices
|
|
265
411
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
266
412
|
this.log.debug('The frontend sent /api/devices');
|
|
267
413
|
const devices = await this.getDevices();
|
|
268
414
|
res.json(devices);
|
|
269
415
|
});
|
|
416
|
+
// Endpoint to view the matterbridge log
|
|
270
417
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
271
418
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
272
419
|
try {
|
|
@@ -279,6 +426,7 @@ export class Frontend {
|
|
|
279
426
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
280
427
|
}
|
|
281
428
|
});
|
|
429
|
+
// Endpoint to view the matter.js log
|
|
282
430
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
283
431
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
284
432
|
try {
|
|
@@ -291,6 +439,7 @@ export class Frontend {
|
|
|
291
439
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
292
440
|
}
|
|
293
441
|
});
|
|
442
|
+
// Endpoint to view the shelly log
|
|
294
443
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
295
444
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
296
445
|
try {
|
|
@@ -303,9 +452,11 @@ export class Frontend {
|
|
|
303
452
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
304
453
|
}
|
|
305
454
|
});
|
|
455
|
+
// Endpoint to download the matterbridge log
|
|
306
456
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
307
457
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
308
458
|
try {
|
|
459
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
309
460
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), fs.constants.F_OK);
|
|
310
461
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'utf8');
|
|
311
462
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), data, 'utf-8');
|
|
@@ -315,6 +466,7 @@ export class Frontend {
|
|
|
315
466
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
316
467
|
}
|
|
317
468
|
res.type('text/plain');
|
|
469
|
+
// res.download(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
318
470
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
319
471
|
if (error) {
|
|
320
472
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
@@ -322,9 +474,11 @@ export class Frontend {
|
|
|
322
474
|
}
|
|
323
475
|
});
|
|
324
476
|
});
|
|
477
|
+
// Endpoint to download the matter log
|
|
325
478
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
326
479
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile)}`);
|
|
327
480
|
try {
|
|
481
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
328
482
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
329
483
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
330
484
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -341,9 +495,11 @@ export class Frontend {
|
|
|
341
495
|
}
|
|
342
496
|
});
|
|
343
497
|
});
|
|
498
|
+
// Endpoint to download the shelly log
|
|
344
499
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
345
500
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
346
501
|
try {
|
|
502
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
347
503
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
348
504
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
349
505
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -360,6 +516,7 @@ export class Frontend {
|
|
|
360
516
|
}
|
|
361
517
|
});
|
|
362
518
|
});
|
|
519
|
+
// Endpoint to download the matterbridge storage directory
|
|
363
520
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
364
521
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
365
522
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
@@ -370,6 +527,7 @@ export class Frontend {
|
|
|
370
527
|
}
|
|
371
528
|
});
|
|
372
529
|
});
|
|
530
|
+
// Endpoint to download the matter storage file
|
|
373
531
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
374
532
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
375
533
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
@@ -380,6 +538,7 @@ export class Frontend {
|
|
|
380
538
|
}
|
|
381
539
|
});
|
|
382
540
|
});
|
|
541
|
+
// Endpoint to download the matterbridge plugin directory
|
|
383
542
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
384
543
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
385
544
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
@@ -390,6 +549,7 @@ export class Frontend {
|
|
|
390
549
|
}
|
|
391
550
|
});
|
|
392
551
|
});
|
|
552
|
+
// Endpoint to download the matterbridge plugin config files
|
|
393
553
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
394
554
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
395
555
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
@@ -400,6 +560,7 @@ export class Frontend {
|
|
|
400
560
|
}
|
|
401
561
|
});
|
|
402
562
|
});
|
|
563
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
403
564
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
404
565
|
this.log.debug('The frontend sent /api/download-backup');
|
|
405
566
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -409,6 +570,7 @@ export class Frontend {
|
|
|
409
570
|
}
|
|
410
571
|
});
|
|
411
572
|
});
|
|
573
|
+
// Endpoint to upload a package
|
|
412
574
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
413
575
|
const { filename } = req.body;
|
|
414
576
|
const file = req.file;
|
|
@@ -418,10 +580,13 @@ export class Frontend {
|
|
|
418
580
|
return;
|
|
419
581
|
}
|
|
420
582
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
583
|
+
// Define the path where the plugin file will be saved
|
|
421
584
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
422
585
|
try {
|
|
586
|
+
// Move the uploaded file to the specified path
|
|
423
587
|
await fs.rename(file.path, filePath);
|
|
424
588
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
589
|
+
// Install the plugin package
|
|
425
590
|
if (filename.endsWith('.tgz')) {
|
|
426
591
|
await spawn.spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
427
592
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
@@ -440,6 +605,7 @@ export class Frontend {
|
|
|
440
605
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
441
606
|
}
|
|
442
607
|
});
|
|
608
|
+
// Fallback for routing (must be the last route)
|
|
443
609
|
this.expressApp.use((req, res) => {
|
|
444
610
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
445
611
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -448,12 +614,15 @@ export class Frontend {
|
|
|
448
614
|
}
|
|
449
615
|
async stop() {
|
|
450
616
|
this.log.debug('Stopping the frontend...');
|
|
617
|
+
// Remove listeners from the express app
|
|
451
618
|
if (this.expressApp) {
|
|
452
619
|
this.expressApp.removeAllListeners();
|
|
453
620
|
this.expressApp = undefined;
|
|
454
621
|
this.log.debug('Frontend app closed successfully');
|
|
455
622
|
}
|
|
623
|
+
// Close the WebSocket server
|
|
456
624
|
if (this.webSocketServer) {
|
|
625
|
+
// Close all active connections
|
|
457
626
|
this.webSocketServer.clients.forEach((client) => {
|
|
458
627
|
if (client.readyState === WebSocket.OPEN) {
|
|
459
628
|
client.close();
|
|
@@ -473,6 +642,7 @@ export class Frontend {
|
|
|
473
642
|
this.webSocketServer.removeAllListeners();
|
|
474
643
|
this.webSocketServer = undefined;
|
|
475
644
|
}
|
|
645
|
+
// Close the http server
|
|
476
646
|
if (this.httpServer) {
|
|
477
647
|
await withTimeout(new Promise((resolve) => {
|
|
478
648
|
this.httpServer?.close((error) => {
|
|
@@ -489,6 +659,7 @@ export class Frontend {
|
|
|
489
659
|
this.httpServer = undefined;
|
|
490
660
|
this.log.debug('Frontend http server closed successfully');
|
|
491
661
|
}
|
|
662
|
+
// Close the https server
|
|
492
663
|
if (this.httpsServer) {
|
|
493
664
|
await withTimeout(new Promise((resolve) => {
|
|
494
665
|
this.httpsServer?.close((error) => {
|
|
@@ -507,6 +678,7 @@ export class Frontend {
|
|
|
507
678
|
}
|
|
508
679
|
this.log.debug('Frontend stopped successfully');
|
|
509
680
|
}
|
|
681
|
+
// Function to format bytes to KB, MB, or GB
|
|
510
682
|
formatMemoryUsage = (bytes) => {
|
|
511
683
|
if (bytes >= 1024 ** 3) {
|
|
512
684
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -518,6 +690,7 @@ export class Frontend {
|
|
|
518
690
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
519
691
|
}
|
|
520
692
|
};
|
|
693
|
+
// Function to format system uptime with only the most significant unit
|
|
521
694
|
formatOsUpTime = (seconds) => {
|
|
522
695
|
if (seconds >= 86400) {
|
|
523
696
|
const days = Math.floor(seconds / 86400);
|
|
@@ -533,8 +706,14 @@ export class Frontend {
|
|
|
533
706
|
}
|
|
534
707
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
535
708
|
};
|
|
709
|
+
/**
|
|
710
|
+
* Retrieves the api settings data.
|
|
711
|
+
*
|
|
712
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
713
|
+
*/
|
|
536
714
|
async getApiSettings() {
|
|
537
715
|
const { lastCpuUsage } = await import('./cli.js');
|
|
716
|
+
// Update the system information
|
|
538
717
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
539
718
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
540
719
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -543,6 +722,7 @@ export class Frontend {
|
|
|
543
722
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
544
723
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
545
724
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
725
|
+
// Update the matterbridge information
|
|
546
726
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
547
727
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
548
728
|
this.matterbridge.matterbridgeInformation.loggerLevel = this.matterbridge.log.logLevel;
|
|
@@ -561,6 +741,12 @@ export class Frontend {
|
|
|
561
741
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
562
742
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
563
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Retrieves the reachable attribute.
|
|
746
|
+
*
|
|
747
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
748
|
+
* @returns {boolean} The reachable attribute.
|
|
749
|
+
*/
|
|
564
750
|
getReachability(device) {
|
|
565
751
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
566
752
|
return false;
|
|
@@ -570,6 +756,12 @@ export class Frontend {
|
|
|
570
756
|
return true;
|
|
571
757
|
return false;
|
|
572
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* Retrieves the power source attribute.
|
|
761
|
+
*
|
|
762
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice object.
|
|
763
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
764
|
+
*/
|
|
573
765
|
getPowerSource(endpoint) {
|
|
574
766
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
575
767
|
return undefined;
|
|
@@ -585,13 +777,21 @@ export class Frontend {
|
|
|
585
777
|
}
|
|
586
778
|
return;
|
|
587
779
|
};
|
|
780
|
+
// Root endpoint
|
|
588
781
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
589
782
|
return powerSource(endpoint);
|
|
783
|
+
// Child endpoints
|
|
590
784
|
for (const child of endpoint.getChildEndpoints()) {
|
|
591
785
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
592
786
|
return powerSource(child);
|
|
593
787
|
}
|
|
594
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* Retrieves the cluster text description from a given device.
|
|
791
|
+
*
|
|
792
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
793
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
794
|
+
*/
|
|
595
795
|
getClusterTextFromDevice(device) {
|
|
596
796
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
597
797
|
return '';
|
|
@@ -632,7 +832,19 @@ export class Frontend {
|
|
|
632
832
|
};
|
|
633
833
|
let attributes = '';
|
|
634
834
|
let supportedModes = [];
|
|
835
|
+
/*
|
|
836
|
+
Object.keys(device.behaviors.supported).forEach((clusterName) => {
|
|
837
|
+
const clusterBehavior = device.behaviors.supported[lowercaseFirstLetter(clusterName)] as ClusterBehavior.Type | undefined;
|
|
838
|
+
// console.log(`Device: ${device.deviceName} => Cluster: ${clusterName} Behavior: ${clusterBehavior?.id}`, clusterBehavior);
|
|
839
|
+
if (clusterBehavior && clusterBehavior.cluster && clusterBehavior.cluster.attributes) {
|
|
840
|
+
Object.entries(clusterBehavior.cluster.attributes).forEach(([attributeName, attribute]) => {
|
|
841
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName} Attribute: ${attributeName}`, attribute);
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
*/
|
|
635
846
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
847
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
636
848
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
637
849
|
return;
|
|
638
850
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -724,8 +936,14 @@ export class Frontend {
|
|
|
724
936
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
725
937
|
attributes += `${getUserLabel(device)} `;
|
|
726
938
|
});
|
|
939
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
727
940
|
return attributes.trimStart().trimEnd();
|
|
728
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
944
|
+
*
|
|
945
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
946
|
+
*/
|
|
729
947
|
getBaseRegisteredPlugins() {
|
|
730
948
|
const baseRegisteredPlugins = [];
|
|
731
949
|
for (const plugin of this.matterbridge.plugins) {
|
|
@@ -764,11 +982,19 @@ export class Frontend {
|
|
|
764
982
|
}
|
|
765
983
|
return baseRegisteredPlugins;
|
|
766
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Retrieves the devices from Matterbridge.
|
|
987
|
+
*
|
|
988
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
989
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices.
|
|
990
|
+
*/
|
|
767
991
|
async getDevices(pluginName) {
|
|
768
992
|
const devices = [];
|
|
769
993
|
this.matterbridge.devices.forEach(async (device) => {
|
|
994
|
+
// Filter by pluginName if provided
|
|
770
995
|
if (pluginName && pluginName !== device.plugin)
|
|
771
996
|
return;
|
|
997
|
+
// Check if the device has the required properties
|
|
772
998
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
773
999
|
return;
|
|
774
1000
|
const cluster = this.getClusterTextFromDevice(device);
|
|
@@ -788,22 +1014,37 @@ export class Frontend {
|
|
|
788
1014
|
});
|
|
789
1015
|
return devices;
|
|
790
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1019
|
+
*
|
|
1020
|
+
* Response for /api/clusters
|
|
1021
|
+
*
|
|
1022
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1023
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1024
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1025
|
+
*/
|
|
791
1026
|
getClusters(pluginName, endpointNumber) {
|
|
792
1027
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
793
1028
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
794
1029
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
795
1030
|
return;
|
|
796
1031
|
}
|
|
1032
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1033
|
+
// Get the device types from the main endpoint
|
|
797
1034
|
const deviceTypes = [];
|
|
798
1035
|
const clusters = [];
|
|
799
1036
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
800
1037
|
deviceTypes.push(d.deviceType);
|
|
801
1038
|
});
|
|
1039
|
+
// Get the clusters from the main endpoint
|
|
802
1040
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
803
1041
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
804
1042
|
return;
|
|
805
1043
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
806
1044
|
return;
|
|
1045
|
+
// console.log(
|
|
1046
|
+
// `${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}`,
|
|
1047
|
+
// );
|
|
807
1048
|
clusters.push({
|
|
808
1049
|
endpoint: endpoint.number.toString(),
|
|
809
1050
|
id: 'main',
|
|
@@ -816,12 +1057,18 @@ export class Frontend {
|
|
|
816
1057
|
attributeLocalValue: attributeValue,
|
|
817
1058
|
});
|
|
818
1059
|
});
|
|
1060
|
+
// Get the child endpoints
|
|
819
1061
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1062
|
+
// if (childEndpoints.length === 0) {
|
|
1063
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1064
|
+
// }
|
|
820
1065
|
childEndpoints.forEach((childEndpoint) => {
|
|
821
1066
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
822
1067
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
823
1068
|
return;
|
|
824
1069
|
}
|
|
1070
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1071
|
+
// Get the device types of the child endpoint
|
|
825
1072
|
const deviceTypes = [];
|
|
826
1073
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
827
1074
|
deviceTypes.push(d.deviceType);
|
|
@@ -831,9 +1078,12 @@ export class Frontend {
|
|
|
831
1078
|
return;
|
|
832
1079
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
833
1080
|
return;
|
|
1081
|
+
// console.log(
|
|
1082
|
+
// `${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}`,
|
|
1083
|
+
// );
|
|
834
1084
|
clusters.push({
|
|
835
1085
|
endpoint: childEndpoint.number.toString(),
|
|
836
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1086
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
837
1087
|
deviceTypes,
|
|
838
1088
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
839
1089
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -846,6 +1096,13 @@ export class Frontend {
|
|
|
846
1096
|
});
|
|
847
1097
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
848
1098
|
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1101
|
+
*
|
|
1102
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1103
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1104
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1105
|
+
*/
|
|
849
1106
|
async wsMessageHandler(client, message) {
|
|
850
1107
|
let data;
|
|
851
1108
|
try {
|
|
@@ -892,32 +1149,41 @@ export class Frontend {
|
|
|
892
1149
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
893
1150
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
894
1151
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1152
|
+
// The install comes from InstallPlugins
|
|
895
1153
|
this.matterbridge.plugins
|
|
896
1154
|
.add(packageName)
|
|
897
1155
|
.then((plugin) => {
|
|
898
1156
|
if (plugin) {
|
|
1157
|
+
// The plugin is not registered
|
|
899
1158
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
900
1159
|
this.matterbridge.plugins
|
|
901
1160
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1161
|
+
// eslint-disable-next-line promise/no-nesting
|
|
902
1162
|
.then(() => {
|
|
903
1163
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
904
1164
|
this.wssSendRefreshRequired('plugins');
|
|
905
1165
|
return;
|
|
906
1166
|
})
|
|
1167
|
+
// eslint-disable-next-line promise/no-nesting
|
|
907
1168
|
.catch((_error) => {
|
|
1169
|
+
//
|
|
908
1170
|
});
|
|
909
1171
|
}
|
|
910
1172
|
else {
|
|
1173
|
+
// The plugin is already registered
|
|
911
1174
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
912
1175
|
this.wssSendRefreshRequired('plugins');
|
|
913
1176
|
this.wssSendRestartRequired();
|
|
914
1177
|
}
|
|
915
1178
|
return;
|
|
916
1179
|
})
|
|
1180
|
+
// eslint-disable-next-line promise/no-nesting
|
|
917
1181
|
.catch((_error) => {
|
|
1182
|
+
//
|
|
918
1183
|
});
|
|
919
1184
|
}
|
|
920
1185
|
else {
|
|
1186
|
+
// The package is matterbridge
|
|
921
1187
|
if (this.matterbridge.restartMode !== '') {
|
|
922
1188
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
923
1189
|
this.matterbridge.shutdownProcess();
|
|
@@ -940,6 +1206,7 @@ export class Frontend {
|
|
|
940
1206
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
941
1207
|
return;
|
|
942
1208
|
}
|
|
1209
|
+
// The package is a plugin
|
|
943
1210
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
944
1211
|
if (plugin) {
|
|
945
1212
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -948,6 +1215,7 @@ export class Frontend {
|
|
|
948
1215
|
this.wssSendRefreshRequired('plugins');
|
|
949
1216
|
this.wssSendRefreshRequired('devices');
|
|
950
1217
|
}
|
|
1218
|
+
// Uninstall the package
|
|
951
1219
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
952
1220
|
spawn
|
|
953
1221
|
.spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -988,6 +1256,7 @@ export class Frontend {
|
|
|
988
1256
|
return;
|
|
989
1257
|
})
|
|
990
1258
|
.catch((_error) => {
|
|
1259
|
+
//
|
|
991
1260
|
});
|
|
992
1261
|
}
|
|
993
1262
|
else {
|
|
@@ -1034,6 +1303,7 @@ export class Frontend {
|
|
|
1034
1303
|
return;
|
|
1035
1304
|
})
|
|
1036
1305
|
.catch((_error) => {
|
|
1306
|
+
//
|
|
1037
1307
|
});
|
|
1038
1308
|
}
|
|
1039
1309
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1271,22 +1541,22 @@ export class Frontend {
|
|
|
1271
1541
|
if (isValidString(data.params.value, 4)) {
|
|
1272
1542
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1273
1543
|
if (data.params.value === 'Debug') {
|
|
1274
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1544
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1275
1545
|
}
|
|
1276
1546
|
else if (data.params.value === 'Info') {
|
|
1277
|
-
await this.matterbridge.setLogLevel("info");
|
|
1547
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1278
1548
|
}
|
|
1279
1549
|
else if (data.params.value === 'Notice') {
|
|
1280
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1550
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1281
1551
|
}
|
|
1282
1552
|
else if (data.params.value === 'Warn') {
|
|
1283
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1553
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1284
1554
|
}
|
|
1285
1555
|
else if (data.params.value === 'Error') {
|
|
1286
|
-
await this.matterbridge.setLogLevel("error");
|
|
1556
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1287
1557
|
}
|
|
1288
1558
|
else if (data.params.value === 'Fatal') {
|
|
1289
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1559
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1290
1560
|
}
|
|
1291
1561
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1292
1562
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1297,6 +1567,7 @@ export class Frontend {
|
|
|
1297
1567
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1298
1568
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1299
1569
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1570
|
+
// Create the file logger for matterbridge
|
|
1300
1571
|
if (data.params.value)
|
|
1301
1572
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbrideLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1302
1573
|
else
|
|
@@ -1461,15 +1732,19 @@ export class Frontend {
|
|
|
1461
1732
|
return;
|
|
1462
1733
|
}
|
|
1463
1734
|
const config = plugin.configJson;
|
|
1735
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1464
1736
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1737
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1465
1738
|
if (select === 'serial')
|
|
1466
1739
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1467
1740
|
if (select === 'name')
|
|
1468
1741
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1469
1742
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1743
|
+
// Remove postfix from the serial if it exists
|
|
1470
1744
|
if (config.postfix) {
|
|
1471
1745
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1472
1746
|
}
|
|
1747
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1473
1748
|
if (isValidArray(config.whiteList, 1)) {
|
|
1474
1749
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1475
1750
|
config.whiteList.push(data.params.serial);
|
|
@@ -1478,6 +1753,7 @@ export class Frontend {
|
|
|
1478
1753
|
config.whiteList.push(data.params.name);
|
|
1479
1754
|
}
|
|
1480
1755
|
}
|
|
1756
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1481
1757
|
if (isValidArray(config.blackList, 1)) {
|
|
1482
1758
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1483
1759
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1507,7 +1783,9 @@ export class Frontend {
|
|
|
1507
1783
|
return;
|
|
1508
1784
|
}
|
|
1509
1785
|
const config = plugin.configJson;
|
|
1786
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1510
1787
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1788
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1511
1789
|
if (select === 'serial')
|
|
1512
1790
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1513
1791
|
if (select === 'name')
|
|
@@ -1516,6 +1794,7 @@ export class Frontend {
|
|
|
1516
1794
|
if (config.postfix) {
|
|
1517
1795
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1518
1796
|
}
|
|
1797
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1519
1798
|
if (isValidArray(config.whiteList, 1)) {
|
|
1520
1799
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1521
1800
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1524,6 +1803,7 @@ export class Frontend {
|
|
|
1524
1803
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1525
1804
|
}
|
|
1526
1805
|
}
|
|
1806
|
+
// Add the serial to the blackList
|
|
1527
1807
|
if (isValidArray(config.blackList)) {
|
|
1528
1808
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1529
1809
|
config.blackList.push(data.params.serial);
|
|
@@ -1556,114 +1836,230 @@ export class Frontend {
|
|
|
1556
1836
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1557
1837
|
}
|
|
1558
1838
|
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1841
|
+
*
|
|
1842
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1843
|
+
* @param {string} time - The time string of the message
|
|
1844
|
+
* @param {string} name - The logger name of the message
|
|
1845
|
+
* @param {string} message - The content of the message.
|
|
1846
|
+
*
|
|
1847
|
+
* @remarks
|
|
1848
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1849
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1850
|
+
* The function sends the message to all connected clients.
|
|
1851
|
+
*/
|
|
1559
1852
|
wssSendMessage(level, time, name, message) {
|
|
1560
1853
|
if (!level || !time || !name || !message)
|
|
1561
1854
|
return;
|
|
1855
|
+
// Remove ANSI escape codes from the message
|
|
1856
|
+
// eslint-disable-next-line no-control-regex
|
|
1562
1857
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1858
|
+
// Remove leading asterisks from the message
|
|
1563
1859
|
message = message.replace(/^\*+/, '');
|
|
1860
|
+
// Replace all occurrences of \t and \n
|
|
1564
1861
|
message = message.replace(/[\t\n]/g, '');
|
|
1862
|
+
// Remove non-printable characters
|
|
1863
|
+
// eslint-disable-next-line no-control-regex
|
|
1565
1864
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1865
|
+
// Replace all occurrences of \" with "
|
|
1566
1866
|
message = message.replace(/\\"/g, '"');
|
|
1867
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1567
1868
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1869
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1568
1870
|
const maxContinuousLength = 100;
|
|
1569
1871
|
const keepStartLength = 20;
|
|
1570
1872
|
const keepEndLength = 20;
|
|
1873
|
+
// Split the message into words
|
|
1571
1874
|
message = message
|
|
1572
1875
|
.split(' ')
|
|
1573
1876
|
.map((word) => {
|
|
1877
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1574
1878
|
if (word.length > maxContinuousLength) {
|
|
1575
1879
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1576
1880
|
}
|
|
1577
1881
|
return word;
|
|
1578
1882
|
})
|
|
1579
1883
|
.join(' ');
|
|
1884
|
+
// Send the message to all connected clients
|
|
1580
1885
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1581
1886
|
if (client.readyState === WebSocket.OPEN) {
|
|
1582
1887
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1583
1888
|
}
|
|
1584
1889
|
});
|
|
1585
1890
|
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
1893
|
+
*
|
|
1894
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
1895
|
+
* possible values:
|
|
1896
|
+
* - 'matterbridgeLatestVersion'
|
|
1897
|
+
* - 'matterbridgeAdvertise'
|
|
1898
|
+
* - 'online'
|
|
1899
|
+
* - 'offline'
|
|
1900
|
+
* - 'reachability'
|
|
1901
|
+
* - 'settings'
|
|
1902
|
+
* - 'plugins'
|
|
1903
|
+
* - 'pluginsRestart'
|
|
1904
|
+
* - 'devices'
|
|
1905
|
+
* - 'fabrics'
|
|
1906
|
+
* - 'sessions'
|
|
1907
|
+
*/
|
|
1586
1908
|
wssSendRefreshRequired(changed = null) {
|
|
1587
1909
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
1910
|
+
// Send the message to all connected clients
|
|
1588
1911
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1589
1912
|
if (client.readyState === WebSocket.OPEN) {
|
|
1590
1913
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1591
1914
|
}
|
|
1592
1915
|
});
|
|
1593
1916
|
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
1919
|
+
*
|
|
1920
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients.
|
|
1921
|
+
*/
|
|
1594
1922
|
wssSendRestartRequired(snackbar = true) {
|
|
1595
1923
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1596
1924
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1597
1925
|
if (snackbar === true)
|
|
1598
1926
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1927
|
+
// Send the message to all connected clients
|
|
1599
1928
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1600
1929
|
if (client.readyState === WebSocket.OPEN) {
|
|
1601
1930
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
1602
1931
|
}
|
|
1603
1932
|
});
|
|
1604
1933
|
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
1936
|
+
*
|
|
1937
|
+
*/
|
|
1605
1938
|
wssSendUpdateRequired() {
|
|
1606
1939
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1607
1940
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
1941
|
+
// Send the message to all connected clients
|
|
1608
1942
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1609
1943
|
if (client.readyState === WebSocket.OPEN) {
|
|
1610
1944
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', params: {} }));
|
|
1611
1945
|
}
|
|
1612
1946
|
});
|
|
1613
1947
|
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Sends a cpu update message to all connected clients.
|
|
1950
|
+
*
|
|
1951
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
1952
|
+
*/
|
|
1614
1953
|
wssSendCpuUpdate(cpuUsage) {
|
|
1615
1954
|
if (hasParameter('debug'))
|
|
1616
1955
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
1956
|
+
// Send the message to all connected clients
|
|
1617
1957
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1618
1958
|
if (client.readyState === WebSocket.OPEN) {
|
|
1619
1959
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1620
1960
|
}
|
|
1621
1961
|
});
|
|
1622
1962
|
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Sends a memory update message to all connected clients.
|
|
1965
|
+
*
|
|
1966
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
1967
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
1968
|
+
* @param {string} rss - The resident set size in bytes.
|
|
1969
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
1970
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
1971
|
+
* @param {string} external - The external memory in bytes.
|
|
1972
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
1973
|
+
*/
|
|
1623
1974
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1624
1975
|
if (hasParameter('debug'))
|
|
1625
1976
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
1977
|
+
// Send the message to all connected clients
|
|
1626
1978
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1627
1979
|
if (client.readyState === WebSocket.OPEN) {
|
|
1628
1980
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1629
1981
|
}
|
|
1630
1982
|
});
|
|
1631
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Sends an uptime update message to all connected clients.
|
|
1986
|
+
*
|
|
1987
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
1988
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
1989
|
+
*/
|
|
1632
1990
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1633
1991
|
if (hasParameter('debug'))
|
|
1634
1992
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
1993
|
+
// Send the message to all connected clients
|
|
1635
1994
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1636
1995
|
if (client.readyState === WebSocket.OPEN) {
|
|
1637
1996
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1638
1997
|
}
|
|
1639
1998
|
});
|
|
1640
1999
|
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Sends an open snackbar message to all connected clients.
|
|
2002
|
+
*
|
|
2003
|
+
* @param {string} message - The message to send.
|
|
2004
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2005
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2006
|
+
*/
|
|
1641
2007
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1642
2008
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2009
|
+
// Send the message to all connected clients
|
|
1643
2010
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1644
2011
|
if (client.readyState === WebSocket.OPEN) {
|
|
1645
2012
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1646
2013
|
}
|
|
1647
2014
|
});
|
|
1648
2015
|
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Sends a close snackbar message to all connected clients.
|
|
2018
|
+
*
|
|
2019
|
+
* @param {string} message - The message to send.
|
|
2020
|
+
*/
|
|
1649
2021
|
wssSendCloseSnackbarMessage(message) {
|
|
1650
2022
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2023
|
+
// Send the message to all connected clients
|
|
1651
2024
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1652
2025
|
if (client.readyState === WebSocket.OPEN) {
|
|
1653
2026
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1654
2027
|
}
|
|
1655
2028
|
});
|
|
1656
2029
|
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2032
|
+
*
|
|
2033
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2034
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2035
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2036
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2037
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2038
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2039
|
+
*
|
|
2040
|
+
* @remarks
|
|
2041
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2042
|
+
* with the updated attribute information.
|
|
2043
|
+
*/
|
|
1657
2044
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1658
2045
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2046
|
+
// Send the message to all connected clients
|
|
1659
2047
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1660
2048
|
if (client.readyState === WebSocket.OPEN) {
|
|
1661
2049
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1662
2050
|
}
|
|
1663
2051
|
});
|
|
1664
2052
|
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Sends a message to all connected clients.
|
|
2055
|
+
*
|
|
2056
|
+
* @param {number} id - The message id.
|
|
2057
|
+
* @param {string} method - The message method.
|
|
2058
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2059
|
+
*/
|
|
1665
2060
|
wssBroadcastMessage(id, method, params) {
|
|
1666
2061
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2062
|
+
// Send the message to all connected clients
|
|
1667
2063
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1668
2064
|
if (client.readyState === WebSocket.OPEN) {
|
|
1669
2065
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1671,3 +2067,4 @@ export class Frontend {
|
|
|
1671
2067
|
});
|
|
1672
2068
|
}
|
|
1673
2069
|
}
|
|
2070
|
+
//# sourceMappingURL=frontend.js.map
|