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