matterbridge 3.2.0-dev-20250801-f7eb2a2 → 3.2.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/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +91 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +78 -3
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +11 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +87 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +83 -6
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +242 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +91 -7
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +93 -7
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +140 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +113 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +51 -13
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +288 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +298 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +65 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +60 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +313 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +448 -23
- package/dist/frontend.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +59 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +47 -0
- package/dist/globalMatterbridge.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +463 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +802 -50
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1351 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +65 -5
- 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 +1354 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1224 -55
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +406 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +344 -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 +243 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +197 -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 +270 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +249 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -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/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.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 +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +123 -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 +84 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +94 -7
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +33 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +40 -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/docs/README-DEV.md +377 -0
- package/docs/README-DOCKER.md +208 -0
- package/docs/README-NGINX.md +235 -0
- package/docs/README-PODMAN.md +109 -0
- package/docs/README-SERVICE.md +212 -0
- package/docs/README.md +618 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/frontend.js
CHANGED
|
@@ -1,30 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Frontend.
|
|
3
|
+
*
|
|
4
|
+
* @file frontend.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2025-01-13
|
|
7
|
+
* @version 1.2.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node modules
|
|
1
25
|
import { createServer } from 'node:http';
|
|
2
26
|
import https from 'node:https';
|
|
3
27
|
import os from 'node:os';
|
|
4
28
|
import path from 'node:path';
|
|
5
29
|
import { existsSync, promises as fs } from 'node:fs';
|
|
6
30
|
import EventEmitter from 'node:events';
|
|
31
|
+
// Third-party modules
|
|
7
32
|
import express from 'express';
|
|
8
33
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
9
34
|
import multer from 'multer';
|
|
35
|
+
// AnsiLogger module
|
|
10
36
|
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
37
|
+
// @matter
|
|
11
38
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
12
39
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter } from './utils/export.js';
|
|
14
42
|
import { plg } from './matterbridgeTypes.js';
|
|
15
43
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
16
44
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
45
|
+
/**
|
|
46
|
+
* Websocket message ID for logging.
|
|
47
|
+
*
|
|
48
|
+
* @constant {number}
|
|
49
|
+
*/
|
|
17
50
|
export const WS_ID_LOG = 0;
|
|
51
|
+
/**
|
|
52
|
+
* Websocket message ID indicating a refresh is needed.
|
|
53
|
+
*
|
|
54
|
+
* @constant {number}
|
|
55
|
+
*/
|
|
18
56
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Websocket message ID indicating a restart is needed.
|
|
59
|
+
*
|
|
60
|
+
* @constant {number}
|
|
61
|
+
*/
|
|
19
62
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
63
|
+
/**
|
|
64
|
+
* Websocket message ID indicating a cpu update.
|
|
65
|
+
*
|
|
66
|
+
* @constant {number}
|
|
67
|
+
*/
|
|
20
68
|
export const WS_ID_CPU_UPDATE = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Websocket message ID indicating a memory update.
|
|
71
|
+
*
|
|
72
|
+
* @constant {number}
|
|
73
|
+
*/
|
|
21
74
|
export const WS_ID_MEMORY_UPDATE = 4;
|
|
75
|
+
/**
|
|
76
|
+
* Websocket message ID indicating an uptime update.
|
|
77
|
+
*
|
|
78
|
+
* @constant {number}
|
|
79
|
+
*/
|
|
22
80
|
export const WS_ID_UPTIME_UPDATE = 5;
|
|
81
|
+
/**
|
|
82
|
+
* Websocket message ID indicating a snackbar message.
|
|
83
|
+
*
|
|
84
|
+
* @constant {number}
|
|
85
|
+
*/
|
|
23
86
|
export const WS_ID_SNACKBAR = 6;
|
|
87
|
+
/**
|
|
88
|
+
* Websocket message ID indicating matterbridge has un update available.
|
|
89
|
+
*
|
|
90
|
+
* @constant {number}
|
|
91
|
+
*/
|
|
24
92
|
export const WS_ID_UPDATE_NEEDED = 7;
|
|
93
|
+
/**
|
|
94
|
+
* Websocket message ID indicating a state update.
|
|
95
|
+
*
|
|
96
|
+
* @constant {number}
|
|
97
|
+
*/
|
|
25
98
|
export const WS_ID_STATEUPDATE = 8;
|
|
99
|
+
/**
|
|
100
|
+
* Websocket message ID indicating to close a permanent snackbar message.
|
|
101
|
+
*
|
|
102
|
+
* @constant {number}
|
|
103
|
+
*/
|
|
26
104
|
export const WS_ID_CLOSE_SNACKBAR = 9;
|
|
105
|
+
/**
|
|
106
|
+
* Websocket message ID indicating a shelly system update.
|
|
107
|
+
* check:
|
|
108
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/check
|
|
109
|
+
* perform:
|
|
110
|
+
* curl -k http://127.0.0.1:8101/api/updates/sys/perform
|
|
111
|
+
*
|
|
112
|
+
* @constant {number}
|
|
113
|
+
*/
|
|
27
114
|
export const WS_ID_SHELLY_SYS_UPDATE = 100;
|
|
115
|
+
/**
|
|
116
|
+
* Websocket message ID indicating a shelly main update.
|
|
117
|
+
* check:
|
|
118
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/check
|
|
119
|
+
* perform:
|
|
120
|
+
* curl -k http://127.0.0.1:8101/api/updates/main/perform
|
|
121
|
+
*
|
|
122
|
+
* @constant {number}
|
|
123
|
+
*/
|
|
28
124
|
export const WS_ID_SHELLY_MAIN_UPDATE = 101;
|
|
29
125
|
export class Frontend extends EventEmitter {
|
|
30
126
|
matterbridge;
|
|
@@ -37,7 +133,7 @@ export class Frontend extends EventEmitter {
|
|
|
37
133
|
constructor(matterbridge) {
|
|
38
134
|
super();
|
|
39
135
|
this.matterbridge = matterbridge;
|
|
40
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
136
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
41
137
|
}
|
|
42
138
|
set logLevel(logLevel) {
|
|
43
139
|
this.log.logLevel = logLevel;
|
|
@@ -45,12 +141,41 @@ export class Frontend extends EventEmitter {
|
|
|
45
141
|
async start(port = 8283) {
|
|
46
142
|
this.port = port;
|
|
47
143
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
144
|
+
// Initialize multer with the upload directory
|
|
48
145
|
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
49
146
|
await fs.mkdir(uploadDir, { recursive: true });
|
|
50
147
|
const upload = multer({ dest: uploadDir });
|
|
148
|
+
// Create the express app that serves the frontend
|
|
51
149
|
this.expressApp = express();
|
|
150
|
+
// Inject logging/debug wrapper for route/middleware registration
|
|
151
|
+
/*
|
|
152
|
+
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
153
|
+
for (const method of methods) {
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
+
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
+
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
158
|
+
try {
|
|
159
|
+
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
160
|
+
return original(path, ...rest);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
*/
|
|
168
|
+
// Log all requests to the server for debugging
|
|
169
|
+
/*
|
|
170
|
+
this.expressApp.use((req, res, next) => {
|
|
171
|
+
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
172
|
+
next();
|
|
173
|
+
});
|
|
174
|
+
*/
|
|
175
|
+
// Serve static files from '/static' endpoint
|
|
52
176
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
53
177
|
if (!hasParameter('ssl')) {
|
|
178
|
+
// Create an HTTP server and attach the express app
|
|
54
179
|
try {
|
|
55
180
|
this.log.debug(`Creating HTTP server...`);
|
|
56
181
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -60,6 +185,7 @@ export class Frontend extends EventEmitter {
|
|
|
60
185
|
this.emit('server_error', error);
|
|
61
186
|
return;
|
|
62
187
|
}
|
|
188
|
+
// Listen on the specified port
|
|
63
189
|
if (hasParameter('ingress')) {
|
|
64
190
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
65
191
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -98,6 +224,7 @@ export class Frontend extends EventEmitter {
|
|
|
98
224
|
let passphrase;
|
|
99
225
|
let httpsServerOptions = {};
|
|
100
226
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
227
|
+
// Load the p12 certificate and the passphrase
|
|
101
228
|
try {
|
|
102
229
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
103
230
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -109,7 +236,7 @@ export class Frontend extends EventEmitter {
|
|
|
109
236
|
}
|
|
110
237
|
try {
|
|
111
238
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
112
|
-
passphrase = passphrase.trim();
|
|
239
|
+
passphrase = passphrase.trim(); // Ensure no extra characters
|
|
113
240
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
114
241
|
}
|
|
115
242
|
catch (error) {
|
|
@@ -120,6 +247,7 @@ export class Frontend extends EventEmitter {
|
|
|
120
247
|
httpsServerOptions = { pfx, passphrase };
|
|
121
248
|
}
|
|
122
249
|
else {
|
|
250
|
+
// Load the SSL certificate, the private key and optionally the CA certificate. If the CA certificate is present, it will be used to create a full chain certificate.
|
|
123
251
|
try {
|
|
124
252
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
125
253
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -149,9 +277,10 @@ export class Frontend extends EventEmitter {
|
|
|
149
277
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
150
278
|
}
|
|
151
279
|
if (hasParameter('mtls')) {
|
|
152
|
-
httpsServerOptions.requestCert = true;
|
|
153
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
280
|
+
httpsServerOptions.requestCert = true; // Request client certificate
|
|
281
|
+
httpsServerOptions.rejectUnauthorized = true; // Require client certificate validation
|
|
154
282
|
}
|
|
283
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
155
284
|
try {
|
|
156
285
|
this.log.debug(`Creating HTTPS server...`);
|
|
157
286
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -161,6 +290,7 @@ export class Frontend extends EventEmitter {
|
|
|
161
290
|
this.emit('server_error', error);
|
|
162
291
|
return;
|
|
163
292
|
}
|
|
293
|
+
// Listen on the specified port
|
|
164
294
|
if (hasParameter('ingress')) {
|
|
165
295
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
166
296
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -190,17 +320,19 @@ export class Frontend extends EventEmitter {
|
|
|
190
320
|
return;
|
|
191
321
|
});
|
|
192
322
|
}
|
|
323
|
+
// Create a WebSocket server and attach it to the http or https server
|
|
193
324
|
const wssPort = this.port;
|
|
194
325
|
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
195
326
|
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
196
327
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
197
328
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
198
329
|
const clientIp = request.socket.remoteAddress;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
330
|
+
// Set the global logger callback for the WebSocketServer
|
|
331
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
332
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
333
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
334
|
+
if (this.matterbridge.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridge.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
335
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
204
336
|
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), callbackLogLevel);
|
|
205
337
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
206
338
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -222,6 +354,7 @@ export class Frontend extends EventEmitter {
|
|
|
222
354
|
}
|
|
223
355
|
});
|
|
224
356
|
ws.on('error', (error) => {
|
|
357
|
+
// istanbul ignore next
|
|
225
358
|
this.log.error(`WebSocket client error: ${error}`);
|
|
226
359
|
});
|
|
227
360
|
});
|
|
@@ -235,6 +368,7 @@ export class Frontend extends EventEmitter {
|
|
|
235
368
|
this.webSocketServer.on('error', (ws, error) => {
|
|
236
369
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
237
370
|
});
|
|
371
|
+
// Subscribe to cli events
|
|
238
372
|
cliEmitter.removeAllListeners();
|
|
239
373
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
240
374
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -245,6 +379,8 @@ export class Frontend extends EventEmitter {
|
|
|
245
379
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
246
380
|
this.wssSendCpuUpdate(cpuUsage);
|
|
247
381
|
});
|
|
382
|
+
// Endpoint to validate login code
|
|
383
|
+
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
248
384
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
249
385
|
const { password } = req.body;
|
|
250
386
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -263,23 +399,27 @@ export class Frontend extends EventEmitter {
|
|
|
263
399
|
this.log.warn('/api/login error wrong password');
|
|
264
400
|
res.json({ valid: false });
|
|
265
401
|
}
|
|
402
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
266
403
|
}
|
|
267
404
|
catch (error) {
|
|
268
405
|
this.log.error('/api/login error getting password');
|
|
269
406
|
res.json({ valid: false });
|
|
270
407
|
}
|
|
271
408
|
});
|
|
409
|
+
// Endpoint to provide health check for docker
|
|
272
410
|
this.expressApp.get('/health', (req, res) => {
|
|
273
411
|
this.log.debug('Express received /health');
|
|
274
412
|
const healthStatus = {
|
|
275
|
-
status: 'ok',
|
|
276
|
-
uptime: process.uptime(),
|
|
277
|
-
timestamp: new Date().toISOString(),
|
|
413
|
+
status: 'ok', // Indicate service is healthy
|
|
414
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
415
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
278
416
|
};
|
|
279
417
|
res.status(200).json(healthStatus);
|
|
280
418
|
});
|
|
419
|
+
// Endpoint to provide memory usage details
|
|
281
420
|
this.expressApp.get('/memory', async (req, res) => {
|
|
282
421
|
this.log.debug('Express received /memory');
|
|
422
|
+
// Memory usage from process
|
|
283
423
|
const memoryUsageRaw = process.memoryUsage();
|
|
284
424
|
const memoryUsage = {
|
|
285
425
|
rss: this.formatMemoryUsage(memoryUsageRaw.rss),
|
|
@@ -288,10 +428,13 @@ export class Frontend extends EventEmitter {
|
|
|
288
428
|
external: this.formatMemoryUsage(memoryUsageRaw.external),
|
|
289
429
|
arrayBuffers: this.formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
290
430
|
};
|
|
431
|
+
// V8 heap statistics
|
|
291
432
|
const { default: v8 } = await import('node:v8');
|
|
292
433
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
293
434
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
435
|
+
// Format heapStats
|
|
294
436
|
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
437
|
+
// Format heapSpaces
|
|
295
438
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
296
439
|
...space,
|
|
297
440
|
space_size: this.formatMemoryUsage(space.space_size),
|
|
@@ -309,19 +452,23 @@ export class Frontend extends EventEmitter {
|
|
|
309
452
|
};
|
|
310
453
|
res.status(200).json(memoryReport);
|
|
311
454
|
});
|
|
455
|
+
// Endpoint to provide settings
|
|
312
456
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
313
457
|
this.log.debug('The frontend sent /api/settings');
|
|
314
458
|
res.json(await this.getApiSettings());
|
|
315
459
|
});
|
|
460
|
+
// Endpoint to provide plugins
|
|
316
461
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
317
462
|
this.log.debug('The frontend sent /api/plugins');
|
|
318
463
|
res.json(this.getPlugins());
|
|
319
464
|
});
|
|
465
|
+
// Endpoint to provide devices
|
|
320
466
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
321
467
|
this.log.debug('The frontend sent /api/devices');
|
|
322
468
|
const devices = await this.getDevices();
|
|
323
469
|
res.json(devices);
|
|
324
470
|
});
|
|
471
|
+
// Endpoint to view the matterbridge log
|
|
325
472
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
326
473
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
327
474
|
try {
|
|
@@ -334,6 +481,7 @@ export class Frontend extends EventEmitter {
|
|
|
334
481
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
335
482
|
}
|
|
336
483
|
});
|
|
484
|
+
// Endpoint to view the matter.js log
|
|
337
485
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
338
486
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
339
487
|
try {
|
|
@@ -346,6 +494,7 @@ export class Frontend extends EventEmitter {
|
|
|
346
494
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
347
495
|
}
|
|
348
496
|
});
|
|
497
|
+
// Endpoint to view the shelly log
|
|
349
498
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
350
499
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
351
500
|
try {
|
|
@@ -358,6 +507,7 @@ export class Frontend extends EventEmitter {
|
|
|
358
507
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
359
508
|
}
|
|
360
509
|
});
|
|
510
|
+
// Endpoint to download the matterbridge log
|
|
361
511
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
362
512
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
363
513
|
try {
|
|
@@ -371,12 +521,14 @@ export class Frontend extends EventEmitter {
|
|
|
371
521
|
}
|
|
372
522
|
res.type('text/plain');
|
|
373
523
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
524
|
+
/* istanbul ignore if */
|
|
374
525
|
if (error) {
|
|
375
526
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
376
527
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
377
528
|
}
|
|
378
529
|
});
|
|
379
530
|
});
|
|
531
|
+
// Endpoint to download the matter log
|
|
380
532
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
381
533
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
382
534
|
try {
|
|
@@ -390,12 +542,14 @@ export class Frontend extends EventEmitter {
|
|
|
390
542
|
}
|
|
391
543
|
res.type('text/plain');
|
|
392
544
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
545
|
+
/* istanbul ignore if */
|
|
393
546
|
if (error) {
|
|
394
547
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
395
548
|
res.status(500).send('Error downloading the matter log file');
|
|
396
549
|
}
|
|
397
550
|
});
|
|
398
551
|
});
|
|
552
|
+
// Endpoint to download the shelly log
|
|
399
553
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
400
554
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
401
555
|
try {
|
|
@@ -409,74 +563,90 @@ export class Frontend extends EventEmitter {
|
|
|
409
563
|
}
|
|
410
564
|
res.type('text/plain');
|
|
411
565
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
566
|
+
/* istanbul ignore if */
|
|
412
567
|
if (error) {
|
|
413
568
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
414
569
|
res.status(500).send('Error downloading Shelly system log file');
|
|
415
570
|
}
|
|
416
571
|
});
|
|
417
572
|
});
|
|
573
|
+
// Endpoint to download the matterbridge storage directory
|
|
418
574
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
419
575
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
420
576
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
421
577
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
578
|
+
/* istanbul ignore if */
|
|
422
579
|
if (error) {
|
|
423
580
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
424
581
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
425
582
|
}
|
|
426
583
|
});
|
|
427
584
|
});
|
|
585
|
+
// Endpoint to download the matter storage file
|
|
428
586
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
429
587
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
430
588
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
431
589
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
590
|
+
/* istanbul ignore if */
|
|
432
591
|
if (error) {
|
|
433
592
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
434
593
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
435
594
|
}
|
|
436
595
|
});
|
|
437
596
|
});
|
|
597
|
+
// Endpoint to download the matterbridge plugin directory
|
|
438
598
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
439
599
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
440
600
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
441
601
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
602
|
+
/* istanbul ignore if */
|
|
442
603
|
if (error) {
|
|
443
604
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
444
605
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
445
606
|
}
|
|
446
607
|
});
|
|
447
608
|
});
|
|
609
|
+
// Endpoint to download the matterbridge plugin config files
|
|
448
610
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
449
611
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
450
612
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
451
613
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
614
|
+
/* istanbul ignore if */
|
|
452
615
|
if (error) {
|
|
453
616
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
454
617
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
455
618
|
}
|
|
456
619
|
});
|
|
457
620
|
});
|
|
621
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
458
622
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
459
623
|
this.log.debug('The frontend sent /api/download-backup');
|
|
460
624
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
625
|
+
/* istanbul ignore if */
|
|
461
626
|
if (error) {
|
|
462
627
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
463
628
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
464
629
|
}
|
|
465
630
|
});
|
|
466
631
|
});
|
|
632
|
+
// Endpoint to upload a package
|
|
467
633
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
468
634
|
const { filename } = req.body;
|
|
469
635
|
const file = req.file;
|
|
636
|
+
/* istanbul ignore if */
|
|
470
637
|
if (!file || !filename) {
|
|
471
638
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
472
639
|
res.status(400).send('Invalid request: file and filename are required');
|
|
473
640
|
return;
|
|
474
641
|
}
|
|
475
642
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
643
|
+
// Define the path where the plugin file will be saved
|
|
476
644
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
477
645
|
try {
|
|
646
|
+
// Move the uploaded file to the specified path
|
|
478
647
|
await fs.rename(file.path, filePath);
|
|
479
648
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
649
|
+
// Install the plugin package
|
|
480
650
|
if (filename.endsWith('.tgz')) {
|
|
481
651
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
482
652
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -496,6 +666,7 @@ export class Frontend extends EventEmitter {
|
|
|
496
666
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
497
667
|
}
|
|
498
668
|
});
|
|
669
|
+
// Fallback for routing (must be the last route)
|
|
499
670
|
this.expressApp.use((req, res) => {
|
|
500
671
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
501
672
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -504,13 +675,16 @@ export class Frontend extends EventEmitter {
|
|
|
504
675
|
}
|
|
505
676
|
async stop() {
|
|
506
677
|
this.log.debug('Stopping the frontend...');
|
|
678
|
+
// Remove listeners from the express app
|
|
507
679
|
if (this.expressApp) {
|
|
508
680
|
this.expressApp.removeAllListeners();
|
|
509
681
|
this.expressApp = undefined;
|
|
510
682
|
this.log.debug('Frontend app closed successfully');
|
|
511
683
|
}
|
|
684
|
+
// Close the WebSocket server
|
|
512
685
|
if (this.webSocketServer) {
|
|
513
686
|
this.log.debug('Closing WebSocket server...');
|
|
687
|
+
// Close all active connections
|
|
514
688
|
this.webSocketServer.clients.forEach((client) => {
|
|
515
689
|
if (client.readyState === WebSocket.OPEN) {
|
|
516
690
|
client.close();
|
|
@@ -519,6 +693,7 @@ export class Frontend extends EventEmitter {
|
|
|
519
693
|
await withTimeout(new Promise((resolve) => {
|
|
520
694
|
this.webSocketServer?.close((error) => {
|
|
521
695
|
if (error) {
|
|
696
|
+
// istanbul ignore next
|
|
522
697
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
523
698
|
}
|
|
524
699
|
else {
|
|
@@ -531,11 +706,13 @@ export class Frontend extends EventEmitter {
|
|
|
531
706
|
this.webSocketServer.removeAllListeners();
|
|
532
707
|
this.webSocketServer = undefined;
|
|
533
708
|
}
|
|
709
|
+
// Close the http server
|
|
534
710
|
if (this.httpServer) {
|
|
535
711
|
this.log.debug('Closing http server...');
|
|
536
712
|
await withTimeout(new Promise((resolve) => {
|
|
537
713
|
this.httpServer?.close((error) => {
|
|
538
714
|
if (error) {
|
|
715
|
+
// istanbul ignore next
|
|
539
716
|
this.log.error(`Error closing http server: ${error}`);
|
|
540
717
|
}
|
|
541
718
|
else {
|
|
@@ -549,11 +726,13 @@ export class Frontend extends EventEmitter {
|
|
|
549
726
|
this.httpServer = undefined;
|
|
550
727
|
this.log.debug('Frontend http server closed successfully');
|
|
551
728
|
}
|
|
729
|
+
// Close the https server
|
|
552
730
|
if (this.httpsServer) {
|
|
553
731
|
this.log.debug('Closing https server...');
|
|
554
732
|
await withTimeout(new Promise((resolve) => {
|
|
555
733
|
this.httpsServer?.close((error) => {
|
|
556
734
|
if (error) {
|
|
735
|
+
// istanbul ignore next
|
|
557
736
|
this.log.error(`Error closing https server: ${error}`);
|
|
558
737
|
}
|
|
559
738
|
else {
|
|
@@ -569,6 +748,7 @@ export class Frontend extends EventEmitter {
|
|
|
569
748
|
}
|
|
570
749
|
this.log.debug('Frontend stopped successfully');
|
|
571
750
|
}
|
|
751
|
+
// Function to format bytes to KB, MB, or GB
|
|
572
752
|
formatMemoryUsage = (bytes) => {
|
|
573
753
|
if (bytes >= 1024 ** 3) {
|
|
574
754
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -580,6 +760,7 @@ export class Frontend extends EventEmitter {
|
|
|
580
760
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
581
761
|
}
|
|
582
762
|
};
|
|
763
|
+
// Function to format system uptime with only the most significant unit
|
|
583
764
|
formatOsUpTime = (seconds) => {
|
|
584
765
|
if (seconds >= 86400) {
|
|
585
766
|
const days = Math.floor(seconds / 86400);
|
|
@@ -595,7 +776,13 @@ export class Frontend extends EventEmitter {
|
|
|
595
776
|
}
|
|
596
777
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
597
778
|
};
|
|
779
|
+
/**
|
|
780
|
+
* Retrieves the api settings data.
|
|
781
|
+
*
|
|
782
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
783
|
+
*/
|
|
598
784
|
async getApiSettings() {
|
|
785
|
+
// Update the system information
|
|
599
786
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
600
787
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
601
788
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -604,6 +791,7 @@ export class Frontend extends EventEmitter {
|
|
|
604
791
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
605
792
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
606
793
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
794
|
+
// Update the matterbridge information
|
|
607
795
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
608
796
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
609
797
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -615,6 +803,7 @@ export class Frontend extends EventEmitter {
|
|
|
615
803
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
616
804
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
617
805
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
806
|
+
// Update the matterbridge information in bridge mode
|
|
618
807
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
|
|
619
808
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
620
809
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -624,6 +813,12 @@ export class Frontend extends EventEmitter {
|
|
|
624
813
|
}
|
|
625
814
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
626
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Retrieves the reachable attribute.
|
|
818
|
+
*
|
|
819
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
820
|
+
* @returns {boolean} The reachable attribute.
|
|
821
|
+
*/
|
|
627
822
|
getReachability(device) {
|
|
628
823
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
629
824
|
return false;
|
|
@@ -635,6 +830,12 @@ export class Frontend extends EventEmitter {
|
|
|
635
830
|
return true;
|
|
636
831
|
return false;
|
|
637
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Retrieves the power source attribute.
|
|
835
|
+
*
|
|
836
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
837
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
838
|
+
*/
|
|
638
839
|
getPowerSource(endpoint) {
|
|
639
840
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
640
841
|
return undefined;
|
|
@@ -650,13 +851,21 @@ export class Frontend extends EventEmitter {
|
|
|
650
851
|
}
|
|
651
852
|
return;
|
|
652
853
|
};
|
|
854
|
+
// Root endpoint
|
|
653
855
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
654
856
|
return powerSource(endpoint);
|
|
857
|
+
// Child endpoints
|
|
655
858
|
for (const child of endpoint.getChildEndpoints()) {
|
|
656
859
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
657
860
|
return powerSource(child);
|
|
658
861
|
}
|
|
659
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
865
|
+
*
|
|
866
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
867
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
868
|
+
*/
|
|
660
869
|
getMatterDataFromDevice(device) {
|
|
661
870
|
if (device.mode === 'server' && device.serverNode) {
|
|
662
871
|
return {
|
|
@@ -668,6 +877,13 @@ export class Frontend extends EventEmitter {
|
|
|
668
877
|
};
|
|
669
878
|
}
|
|
670
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Retrieves the cluster text description from a given device.
|
|
882
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
883
|
+
*
|
|
884
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
885
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
886
|
+
*/
|
|
671
887
|
getClusterTextFromDevice(device) {
|
|
672
888
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
673
889
|
return '';
|
|
@@ -678,6 +894,7 @@ export class Frontend extends EventEmitter {
|
|
|
678
894
|
if (composed)
|
|
679
895
|
return 'Composed: ' + composed.value;
|
|
680
896
|
}
|
|
897
|
+
// istanbul ignore next cause is not reachable
|
|
681
898
|
return '';
|
|
682
899
|
};
|
|
683
900
|
const getFixedLabel = (device) => {
|
|
@@ -687,11 +904,13 @@ export class Frontend extends EventEmitter {
|
|
|
687
904
|
if (composed)
|
|
688
905
|
return 'Composed: ' + composed.value;
|
|
689
906
|
}
|
|
907
|
+
// istanbul ignore next cause is not reacheable
|
|
690
908
|
return '';
|
|
691
909
|
};
|
|
692
910
|
let attributes = '';
|
|
693
911
|
let supportedModes = [];
|
|
694
912
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
913
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
695
914
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
696
915
|
return;
|
|
697
916
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -781,11 +1000,17 @@ export class Frontend extends EventEmitter {
|
|
|
781
1000
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
782
1001
|
attributes += `${getUserLabel(device)} `;
|
|
783
1002
|
});
|
|
1003
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
784
1004
|
return attributes.trimStart().trimEnd();
|
|
785
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1008
|
+
*
|
|
1009
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1010
|
+
*/
|
|
786
1011
|
getPlugins() {
|
|
787
1012
|
if (this.matterbridge.hasCleanupStarted)
|
|
788
|
-
return [];
|
|
1013
|
+
return []; // Skip if cleanup has started
|
|
789
1014
|
const baseRegisteredPlugins = [];
|
|
790
1015
|
for (const plugin of this.matterbridge.plugins) {
|
|
791
1016
|
baseRegisteredPlugins.push({
|
|
@@ -815,6 +1040,7 @@ export class Frontend extends EventEmitter {
|
|
|
815
1040
|
schemaJson: plugin.schemaJson,
|
|
816
1041
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
817
1042
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1043
|
+
// Childbridge mode specific data
|
|
818
1044
|
paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
|
|
819
1045
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
|
|
820
1046
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
|
|
@@ -824,13 +1050,21 @@ export class Frontend extends EventEmitter {
|
|
|
824
1050
|
}
|
|
825
1051
|
return baseRegisteredPlugins;
|
|
826
1052
|
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Retrieves the devices from Matterbridge.
|
|
1055
|
+
*
|
|
1056
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1057
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1058
|
+
*/
|
|
827
1059
|
async getDevices(pluginName) {
|
|
828
1060
|
if (this.matterbridge.hasCleanupStarted)
|
|
829
|
-
return [];
|
|
1061
|
+
return []; // Skip if cleanup has started
|
|
830
1062
|
const devices = [];
|
|
831
1063
|
for (const device of this.matterbridge.devices.array()) {
|
|
1064
|
+
// Filter by pluginName if provided
|
|
832
1065
|
if (pluginName && pluginName !== device.plugin)
|
|
833
1066
|
continue;
|
|
1067
|
+
// Check if the device has the required properties
|
|
834
1068
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
835
1069
|
continue;
|
|
836
1070
|
devices.push({
|
|
@@ -850,22 +1084,37 @@ export class Frontend extends EventEmitter {
|
|
|
850
1084
|
}
|
|
851
1085
|
return devices;
|
|
852
1086
|
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1089
|
+
*
|
|
1090
|
+
* Response for /api/clusters
|
|
1091
|
+
*
|
|
1092
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1093
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1094
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1095
|
+
*/
|
|
853
1096
|
getClusters(pluginName, endpointNumber) {
|
|
854
1097
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
855
1098
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
856
1099
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
857
1100
|
return;
|
|
858
1101
|
}
|
|
1102
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1103
|
+
// Get the device types from the main endpoint
|
|
859
1104
|
const deviceTypes = [];
|
|
860
1105
|
const clusters = [];
|
|
861
1106
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
862
1107
|
deviceTypes.push(d.deviceType);
|
|
863
1108
|
});
|
|
1109
|
+
// Get the clusters from the main endpoint
|
|
864
1110
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
865
1111
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
866
1112
|
return;
|
|
867
1113
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
868
1114
|
return;
|
|
1115
|
+
// console.log(
|
|
1116
|
+
// `${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}`,
|
|
1117
|
+
// );
|
|
869
1118
|
clusters.push({
|
|
870
1119
|
endpoint: endpoint.number.toString(),
|
|
871
1120
|
id: 'main',
|
|
@@ -878,12 +1127,19 @@ export class Frontend extends EventEmitter {
|
|
|
878
1127
|
attributeLocalValue: attributeValue,
|
|
879
1128
|
});
|
|
880
1129
|
});
|
|
1130
|
+
// Get the child endpoints
|
|
881
1131
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1132
|
+
// if (childEndpoints.length === 0) {
|
|
1133
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1134
|
+
// }
|
|
882
1135
|
childEndpoints.forEach((childEndpoint) => {
|
|
1136
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
883
1137
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
884
1138
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
885
1139
|
return;
|
|
886
1140
|
}
|
|
1141
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1142
|
+
// Get the device types of the child endpoint
|
|
887
1143
|
const deviceTypes = [];
|
|
888
1144
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
889
1145
|
deviceTypes.push(d.deviceType);
|
|
@@ -893,9 +1149,12 @@ export class Frontend extends EventEmitter {
|
|
|
893
1149
|
return;
|
|
894
1150
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
895
1151
|
return;
|
|
1152
|
+
// console.log(
|
|
1153
|
+
// `${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}`,
|
|
1154
|
+
// );
|
|
896
1155
|
clusters.push({
|
|
897
1156
|
endpoint: childEndpoint.number.toString(),
|
|
898
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1157
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
899
1158
|
deviceTypes,
|
|
900
1159
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
901
1160
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -908,6 +1167,13 @@ export class Frontend extends EventEmitter {
|
|
|
908
1167
|
});
|
|
909
1168
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
910
1169
|
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1172
|
+
*
|
|
1173
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1174
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1175
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1176
|
+
*/
|
|
911
1177
|
async wsMessageHandler(client, message) {
|
|
912
1178
|
let data;
|
|
913
1179
|
try {
|
|
@@ -954,33 +1220,45 @@ export class Frontend extends EventEmitter {
|
|
|
954
1220
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
955
1221
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
956
1222
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1223
|
+
// The install comes from InstallPlugins
|
|
957
1224
|
this.matterbridge.plugins
|
|
958
1225
|
.add(packageName)
|
|
959
1226
|
.then((plugin) => {
|
|
1227
|
+
// istanbul ignore next if
|
|
960
1228
|
if (plugin) {
|
|
1229
|
+
// The plugin is not registered
|
|
961
1230
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
962
1231
|
this.matterbridge.plugins
|
|
963
1232
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1233
|
+
// eslint-disable-next-line promise/no-nesting
|
|
964
1234
|
.then(() => {
|
|
965
1235
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
966
1236
|
this.wssSendRefreshRequired('plugins');
|
|
967
1237
|
return;
|
|
968
1238
|
})
|
|
1239
|
+
// eslint-disable-next-line promise/no-nesting
|
|
969
1240
|
.catch((_error) => {
|
|
1241
|
+
//
|
|
970
1242
|
});
|
|
971
1243
|
}
|
|
972
1244
|
else {
|
|
1245
|
+
// The plugin is already registered
|
|
973
1246
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
974
1247
|
this.wssSendRefreshRequired('plugins');
|
|
975
1248
|
this.wssSendRestartRequired(true, true);
|
|
976
1249
|
}
|
|
977
1250
|
return;
|
|
978
1251
|
})
|
|
1252
|
+
// eslint-disable-next-line promise/no-nesting
|
|
979
1253
|
.catch((_error) => {
|
|
1254
|
+
//
|
|
980
1255
|
});
|
|
981
1256
|
}
|
|
982
1257
|
else {
|
|
1258
|
+
// The package is matterbridge
|
|
1259
|
+
// istanbul ignore next
|
|
983
1260
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1261
|
+
// istanbul ignore next if
|
|
984
1262
|
if (this.matterbridge.restartMode !== '') {
|
|
985
1263
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
986
1264
|
this.matterbridge.shutdownProcess();
|
|
@@ -1002,7 +1280,9 @@ export class Frontend extends EventEmitter {
|
|
|
1002
1280
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1003
1281
|
return;
|
|
1004
1282
|
}
|
|
1283
|
+
// The package is a plugin
|
|
1005
1284
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1285
|
+
// istanbul ignore next if
|
|
1006
1286
|
if (plugin) {
|
|
1007
1287
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1008
1288
|
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
@@ -1010,6 +1290,7 @@ export class Frontend extends EventEmitter {
|
|
|
1010
1290
|
this.wssSendRefreshRequired('plugins');
|
|
1011
1291
|
this.wssSendRefreshRequired('devices');
|
|
1012
1292
|
}
|
|
1293
|
+
// Uninstall the package
|
|
1013
1294
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1014
1295
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1015
1296
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1050,6 +1331,7 @@ export class Frontend extends EventEmitter {
|
|
|
1050
1331
|
return;
|
|
1051
1332
|
})
|
|
1052
1333
|
.catch((_error) => {
|
|
1334
|
+
//
|
|
1053
1335
|
});
|
|
1054
1336
|
}
|
|
1055
1337
|
else {
|
|
@@ -1096,6 +1378,7 @@ export class Frontend extends EventEmitter {
|
|
|
1096
1378
|
return;
|
|
1097
1379
|
})
|
|
1098
1380
|
.catch((_error) => {
|
|
1381
|
+
//
|
|
1099
1382
|
});
|
|
1100
1383
|
}
|
|
1101
1384
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1121,6 +1404,7 @@ export class Frontend extends EventEmitter {
|
|
|
1121
1404
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1122
1405
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1123
1406
|
if (plugin.serverNode) {
|
|
1407
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1124
1408
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1125
1409
|
plugin.serverNode = undefined;
|
|
1126
1410
|
}
|
|
@@ -1131,15 +1415,16 @@ export class Frontend extends EventEmitter {
|
|
|
1131
1415
|
}
|
|
1132
1416
|
}
|
|
1133
1417
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1134
|
-
plugin.restartRequired = false;
|
|
1418
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1135
1419
|
let needRestart = 0;
|
|
1136
1420
|
for (const plugin of this.matterbridge.plugins) {
|
|
1137
1421
|
if (plugin.restartRequired)
|
|
1138
1422
|
needRestart++;
|
|
1139
1423
|
}
|
|
1140
1424
|
if (needRestart === 0) {
|
|
1141
|
-
this.wssSendRestartNotRequired(true);
|
|
1425
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1142
1426
|
}
|
|
1427
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1143
1428
|
if (plugin.serverNode)
|
|
1144
1429
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1145
1430
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1245,6 +1530,8 @@ export class Frontend extends EventEmitter {
|
|
|
1245
1530
|
else if (data.method === '/api/advertise') {
|
|
1246
1531
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1247
1532
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1533
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1534
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1248
1535
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1249
1536
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1250
1537
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1367,22 +1654,22 @@ export class Frontend extends EventEmitter {
|
|
|
1367
1654
|
if (isValidString(data.params.value, 4)) {
|
|
1368
1655
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1369
1656
|
if (data.params.value === 'Debug') {
|
|
1370
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1657
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1371
1658
|
}
|
|
1372
1659
|
else if (data.params.value === 'Info') {
|
|
1373
|
-
await this.matterbridge.setLogLevel("info");
|
|
1660
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1374
1661
|
}
|
|
1375
1662
|
else if (data.params.value === 'Notice') {
|
|
1376
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1663
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1377
1664
|
}
|
|
1378
1665
|
else if (data.params.value === 'Warn') {
|
|
1379
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1666
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1380
1667
|
}
|
|
1381
1668
|
else if (data.params.value === 'Error') {
|
|
1382
|
-
await this.matterbridge.setLogLevel("error");
|
|
1669
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1383
1670
|
}
|
|
1384
1671
|
else if (data.params.value === 'Fatal') {
|
|
1385
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1672
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1386
1673
|
}
|
|
1387
1674
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1388
1675
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1393,6 +1680,7 @@ export class Frontend extends EventEmitter {
|
|
|
1393
1680
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1394
1681
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1395
1682
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1683
|
+
// Create the file logger for matterbridge
|
|
1396
1684
|
if (data.params.value)
|
|
1397
1685
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1398
1686
|
else
|
|
@@ -1439,6 +1727,7 @@ export class Frontend extends EventEmitter {
|
|
|
1439
1727
|
});
|
|
1440
1728
|
}
|
|
1441
1729
|
catch (error) {
|
|
1730
|
+
/* istanbul ignore next */
|
|
1442
1731
|
this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1443
1732
|
}
|
|
1444
1733
|
}
|
|
@@ -1447,6 +1736,7 @@ export class Frontend extends EventEmitter {
|
|
|
1447
1736
|
Logger.removeLogger('matterfilelogger');
|
|
1448
1737
|
}
|
|
1449
1738
|
catch (error) {
|
|
1739
|
+
/* istanbul ignore next */
|
|
1450
1740
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1451
1741
|
}
|
|
1452
1742
|
}
|
|
@@ -1559,15 +1849,19 @@ export class Frontend extends EventEmitter {
|
|
|
1559
1849
|
return;
|
|
1560
1850
|
}
|
|
1561
1851
|
const config = plugin.configJson;
|
|
1852
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1562
1853
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1854
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1563
1855
|
if (select === 'serial')
|
|
1564
1856
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1565
1857
|
if (select === 'name')
|
|
1566
1858
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1567
1859
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1860
|
+
// Remove postfix from the serial if it exists
|
|
1568
1861
|
if (config.postfix) {
|
|
1569
1862
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1570
1863
|
}
|
|
1864
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1571
1865
|
if (isValidArray(config.whiteList, 1)) {
|
|
1572
1866
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1573
1867
|
config.whiteList.push(data.params.serial);
|
|
@@ -1576,6 +1870,7 @@ export class Frontend extends EventEmitter {
|
|
|
1576
1870
|
config.whiteList.push(data.params.name);
|
|
1577
1871
|
}
|
|
1578
1872
|
}
|
|
1873
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1579
1874
|
if (isValidArray(config.blackList, 1)) {
|
|
1580
1875
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1581
1876
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1606,7 +1901,9 @@ export class Frontend extends EventEmitter {
|
|
|
1606
1901
|
return;
|
|
1607
1902
|
}
|
|
1608
1903
|
const config = plugin.configJson;
|
|
1904
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1609
1905
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1906
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1610
1907
|
if (select === 'serial')
|
|
1611
1908
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1612
1909
|
if (select === 'name')
|
|
@@ -1615,6 +1912,7 @@ export class Frontend extends EventEmitter {
|
|
|
1615
1912
|
if (config.postfix) {
|
|
1616
1913
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1617
1914
|
}
|
|
1915
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1618
1916
|
if (isValidArray(config.whiteList, 1)) {
|
|
1619
1917
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1620
1918
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1623,6 +1921,7 @@ export class Frontend extends EventEmitter {
|
|
|
1623
1921
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1624
1922
|
}
|
|
1625
1923
|
}
|
|
1924
|
+
// Add the serial to the blackList
|
|
1626
1925
|
if (isValidArray(config.blackList)) {
|
|
1627
1926
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1628
1927
|
config.blackList.push(data.params.serial);
|
|
@@ -1656,126 +1955,251 @@ export class Frontend extends EventEmitter {
|
|
|
1656
1955
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1657
1956
|
}
|
|
1658
1957
|
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1960
|
+
*
|
|
1961
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1962
|
+
* @param {string} time - The time string of the message
|
|
1963
|
+
* @param {string} name - The logger name of the message
|
|
1964
|
+
* @param {string} message - The content of the message.
|
|
1965
|
+
*
|
|
1966
|
+
* @remarks
|
|
1967
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1968
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1969
|
+
* The function sends the message to all connected clients.
|
|
1970
|
+
*/
|
|
1659
1971
|
wssSendMessage(level, time, name, message) {
|
|
1660
1972
|
if (!level || !time || !name || !message)
|
|
1661
1973
|
return;
|
|
1974
|
+
// Remove ANSI escape codes from the message
|
|
1975
|
+
// eslint-disable-next-line no-control-regex
|
|
1662
1976
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1977
|
+
// Remove leading asterisks from the message
|
|
1663
1978
|
message = message.replace(/^\*+/, '');
|
|
1979
|
+
// Replace all occurrences of \t and \n
|
|
1664
1980
|
message = message.replace(/[\t\n]/g, '');
|
|
1981
|
+
// Remove non-printable characters
|
|
1982
|
+
// eslint-disable-next-line no-control-regex
|
|
1665
1983
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1984
|
+
// Replace all occurrences of \" with "
|
|
1666
1985
|
message = message.replace(/\\"/g, '"');
|
|
1986
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1667
1987
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1988
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1668
1989
|
const maxContinuousLength = 100;
|
|
1669
1990
|
const keepStartLength = 20;
|
|
1670
1991
|
const keepEndLength = 20;
|
|
1992
|
+
// Split the message into words
|
|
1671
1993
|
message = message
|
|
1672
1994
|
.split(' ')
|
|
1673
1995
|
.map((word) => {
|
|
1996
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1674
1997
|
if (word.length > maxContinuousLength) {
|
|
1675
1998
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1676
1999
|
}
|
|
1677
2000
|
return word;
|
|
1678
2001
|
})
|
|
1679
2002
|
.join(' ');
|
|
2003
|
+
// Send the message to all connected clients
|
|
1680
2004
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1681
2005
|
if (client.readyState === WebSocket.OPEN) {
|
|
1682
2006
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1683
2007
|
}
|
|
1684
2008
|
});
|
|
1685
2009
|
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2012
|
+
*
|
|
2013
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
2014
|
+
* possible values:
|
|
2015
|
+
* - 'matterbridgeLatestVersion'
|
|
2016
|
+
* - 'matterbridgeDevVersion'
|
|
2017
|
+
* - 'matterbridgeAdvertise'
|
|
2018
|
+
* - 'online'
|
|
2019
|
+
* - 'offline'
|
|
2020
|
+
* - 'reachability'
|
|
2021
|
+
* - 'settings'
|
|
2022
|
+
* - 'plugins'
|
|
2023
|
+
* - 'pluginsRestart'
|
|
2024
|
+
* - 'devices'
|
|
2025
|
+
* - 'fabrics'
|
|
2026
|
+
* - 'sessions'
|
|
2027
|
+
*/
|
|
1686
2028
|
wssSendRefreshRequired(changed = null) {
|
|
1687
2029
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2030
|
+
// Send the message to all connected clients
|
|
1688
2031
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1689
2032
|
if (client.readyState === WebSocket.OPEN) {
|
|
1690
2033
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1691
2034
|
}
|
|
1692
2035
|
});
|
|
1693
2036
|
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2039
|
+
*
|
|
2040
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2041
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2042
|
+
*/
|
|
1694
2043
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1695
2044
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1696
2045
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1697
2046
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1698
2047
|
if (snackbar === true)
|
|
1699
2048
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2049
|
+
// Send the message to all connected clients
|
|
1700
2050
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1701
2051
|
if (client.readyState === WebSocket.OPEN) {
|
|
1702
2052
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
|
|
1703
2053
|
}
|
|
1704
2054
|
});
|
|
1705
2055
|
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2058
|
+
*
|
|
2059
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
|
|
2060
|
+
*/
|
|
1706
2061
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1707
2062
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1708
2063
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1709
2064
|
if (snackbar === true)
|
|
1710
2065
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2066
|
+
// Send the message to all connected clients
|
|
1711
2067
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1712
2068
|
if (client.readyState === WebSocket.OPEN) {
|
|
1713
2069
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
|
|
1714
2070
|
}
|
|
1715
2071
|
});
|
|
1716
2072
|
}
|
|
2073
|
+
/**
|
|
2074
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2075
|
+
*
|
|
2076
|
+
* @param {boolean} devVersion - If true, the update is for a development version.
|
|
2077
|
+
*/
|
|
1717
2078
|
wssSendUpdateRequired(devVersion = false) {
|
|
1718
2079
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1719
2080
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2081
|
+
// Send the message to all connected clients
|
|
1720
2082
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1721
2083
|
if (client.readyState === WebSocket.OPEN) {
|
|
1722
2084
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
|
|
1723
2085
|
}
|
|
1724
2086
|
});
|
|
1725
2087
|
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Sends a cpu update message to all connected clients.
|
|
2090
|
+
*
|
|
2091
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2092
|
+
*/
|
|
1726
2093
|
wssSendCpuUpdate(cpuUsage) {
|
|
1727
2094
|
if (hasParameter('debug'))
|
|
1728
2095
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2096
|
+
// Send the message to all connected clients
|
|
1729
2097
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1730
2098
|
if (client.readyState === WebSocket.OPEN) {
|
|
1731
2099
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1732
2100
|
}
|
|
1733
2101
|
});
|
|
1734
2102
|
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Sends a memory update message to all connected clients.
|
|
2105
|
+
*
|
|
2106
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2107
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2108
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2109
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2110
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2111
|
+
* @param {string} external - The external memory in bytes.
|
|
2112
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2113
|
+
*/
|
|
1735
2114
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1736
2115
|
if (hasParameter('debug'))
|
|
1737
2116
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2117
|
+
// Send the message to all connected clients
|
|
1738
2118
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1739
2119
|
if (client.readyState === WebSocket.OPEN) {
|
|
1740
2120
|
client.send(JSON.stringify({ id: WS_ID_MEMORY_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', params: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } }));
|
|
1741
2121
|
}
|
|
1742
2122
|
});
|
|
1743
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Sends an uptime update message to all connected clients.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2128
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2129
|
+
*/
|
|
1744
2130
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1745
2131
|
if (hasParameter('debug'))
|
|
1746
2132
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2133
|
+
// Send the message to all connected clients
|
|
1747
2134
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1748
2135
|
if (client.readyState === WebSocket.OPEN) {
|
|
1749
2136
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1750
2137
|
}
|
|
1751
2138
|
});
|
|
1752
2139
|
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Sends an open snackbar message to all connected clients.
|
|
2142
|
+
*
|
|
2143
|
+
* @param {string} message - The message to send.
|
|
2144
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2145
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2146
|
+
*/
|
|
1753
2147
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1754
2148
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2149
|
+
// Send the message to all connected clients
|
|
1755
2150
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1756
2151
|
if (client.readyState === WebSocket.OPEN) {
|
|
1757
2152
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1758
2153
|
}
|
|
1759
2154
|
});
|
|
1760
2155
|
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Sends a close snackbar message to all connected clients.
|
|
2158
|
+
*
|
|
2159
|
+
* @param {string} message - The message to send.
|
|
2160
|
+
*/
|
|
1761
2161
|
wssSendCloseSnackbarMessage(message) {
|
|
1762
2162
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2163
|
+
// Send the message to all connected clients
|
|
1763
2164
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1764
2165
|
if (client.readyState === WebSocket.OPEN) {
|
|
1765
2166
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1766
2167
|
}
|
|
1767
2168
|
});
|
|
1768
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2172
|
+
*
|
|
2173
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2174
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2175
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2176
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2177
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2178
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2179
|
+
*
|
|
2180
|
+
* @remarks
|
|
2181
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2182
|
+
* with the updated attribute information.
|
|
2183
|
+
*/
|
|
1769
2184
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1770
2185
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2186
|
+
// Send the message to all connected clients
|
|
1771
2187
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1772
2188
|
if (client.readyState === WebSocket.OPEN) {
|
|
1773
2189
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1774
2190
|
}
|
|
1775
2191
|
});
|
|
1776
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Sends a message to all connected clients.
|
|
2195
|
+
*
|
|
2196
|
+
* @param {number} id - The message id.
|
|
2197
|
+
* @param {string} method - The message method.
|
|
2198
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2199
|
+
*/
|
|
1777
2200
|
wssBroadcastMessage(id, method, params) {
|
|
1778
2201
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2202
|
+
// Send the message to all connected clients
|
|
1779
2203
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1780
2204
|
if (client.readyState === WebSocket.OPEN) {
|
|
1781
2205
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1783,3 +2207,4 @@ export class Frontend extends EventEmitter {
|
|
|
1783
2207
|
});
|
|
1784
2208
|
}
|
|
1785
2209
|
}
|
|
2210
|
+
//# sourceMappingURL=frontend.js.map
|