matterbridge 3.1.8-dev-20250727-b8987de → 3.1.8
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 +1 -1
- 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 +449 -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 +803 -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 +1348 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1214 -54
- 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 +233 -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 +68 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +63 -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 +74 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +81 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +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/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.dd4772c9.js → main.6ab99f2a.js} +3 -3
- package/frontend/build/static/js/{main.dd4772c9.js.map → main.6ab99f2a.js.map} +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/vitest/matterbridge.test.ts +0 -218
- /package/frontend/build/static/js/{main.dd4772c9.js.LICENSE.txt → main.6ab99f2a.js.LICENSE.txt} +0 -0
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,9 +507,11 @@ 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 {
|
|
514
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
364
515
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), fs.constants.F_OK);
|
|
365
516
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), 'utf8');
|
|
366
517
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), data, 'utf-8');
|
|
@@ -371,15 +522,18 @@ export class Frontend extends EventEmitter {
|
|
|
371
522
|
}
|
|
372
523
|
res.type('text/plain');
|
|
373
524
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterbridgeLoggerFile), 'matterbridge.log', (error) => {
|
|
525
|
+
/* istanbul ignore if */
|
|
374
526
|
if (error) {
|
|
375
527
|
this.log.error(`Error downloading log file ${this.matterbridge.matterbridgeLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
376
528
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
377
529
|
}
|
|
378
530
|
});
|
|
379
531
|
});
|
|
532
|
+
// Endpoint to download the matter log
|
|
380
533
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
381
534
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile)}`);
|
|
382
535
|
try {
|
|
536
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
383
537
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), fs.constants.F_OK);
|
|
384
538
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterLoggerFile), 'utf8');
|
|
385
539
|
await fs.writeFile(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), data, 'utf-8');
|
|
@@ -390,15 +544,18 @@ export class Frontend extends EventEmitter {
|
|
|
390
544
|
}
|
|
391
545
|
res.type('text/plain');
|
|
392
546
|
res.download(path.join(os.tmpdir(), this.matterbridge.matterLoggerFile), 'matter.log', (error) => {
|
|
547
|
+
/* istanbul ignore if */
|
|
393
548
|
if (error) {
|
|
394
549
|
this.log.error(`Error downloading log file ${this.matterbridge.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
395
550
|
res.status(500).send('Error downloading the matter log file');
|
|
396
551
|
}
|
|
397
552
|
});
|
|
398
553
|
});
|
|
554
|
+
// Endpoint to download the shelly log
|
|
399
555
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
400
556
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
401
557
|
try {
|
|
558
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
402
559
|
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
403
560
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
404
561
|
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
@@ -409,74 +566,90 @@ export class Frontend extends EventEmitter {
|
|
|
409
566
|
}
|
|
410
567
|
res.type('text/plain');
|
|
411
568
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
569
|
+
/* istanbul ignore if */
|
|
412
570
|
if (error) {
|
|
413
571
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
414
572
|
res.status(500).send('Error downloading Shelly system log file');
|
|
415
573
|
}
|
|
416
574
|
});
|
|
417
575
|
});
|
|
576
|
+
// Endpoint to download the matterbridge storage directory
|
|
418
577
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
419
578
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
420
579
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.nodeStorageName));
|
|
421
580
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.nodeStorageName}.zip`), `matterbridge.${this.matterbridge.nodeStorageName}.zip`, (error) => {
|
|
581
|
+
/* istanbul ignore if */
|
|
422
582
|
if (error) {
|
|
423
583
|
this.log.error(`Error downloading file ${`matterbridge.${this.matterbridge.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
424
584
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
425
585
|
}
|
|
426
586
|
});
|
|
427
587
|
});
|
|
588
|
+
// Endpoint to download the matter storage file
|
|
428
589
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
429
590
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
430
591
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterStorageName));
|
|
431
592
|
res.download(path.join(os.tmpdir(), `matterbridge.${this.matterbridge.matterStorageName}.zip`), `matterbridge.${this.matterbridge.matterStorageName}.zip`, (error) => {
|
|
593
|
+
/* istanbul ignore if */
|
|
432
594
|
if (error) {
|
|
433
595
|
this.log.error(`Error downloading the matter storage matterbridge.${this.matterbridge.matterStorageName}.zip: ${error instanceof Error ? error.message : error}`);
|
|
434
596
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
435
597
|
}
|
|
436
598
|
});
|
|
437
599
|
});
|
|
600
|
+
// Endpoint to download the matterbridge plugin directory
|
|
438
601
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
439
602
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
440
603
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
441
604
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
605
|
+
/* istanbul ignore if */
|
|
442
606
|
if (error) {
|
|
443
607
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
444
608
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
445
609
|
}
|
|
446
610
|
});
|
|
447
611
|
});
|
|
612
|
+
// Endpoint to download the matterbridge plugin config files
|
|
448
613
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
449
614
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
450
615
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
451
616
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
617
|
+
/* istanbul ignore if */
|
|
452
618
|
if (error) {
|
|
453
619
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
454
620
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
455
621
|
}
|
|
456
622
|
});
|
|
457
623
|
});
|
|
624
|
+
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
458
625
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
459
626
|
this.log.debug('The frontend sent /api/download-backup');
|
|
460
627
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
628
|
+
/* istanbul ignore if */
|
|
461
629
|
if (error) {
|
|
462
630
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
463
631
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
464
632
|
}
|
|
465
633
|
});
|
|
466
634
|
});
|
|
635
|
+
// Endpoint to upload a package
|
|
467
636
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
468
637
|
const { filename } = req.body;
|
|
469
638
|
const file = req.file;
|
|
639
|
+
/* istanbul ignore if */
|
|
470
640
|
if (!file || !filename) {
|
|
471
641
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
472
642
|
res.status(400).send('Invalid request: file and filename are required');
|
|
473
643
|
return;
|
|
474
644
|
}
|
|
475
645
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
646
|
+
// Define the path where the plugin file will be saved
|
|
476
647
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
477
648
|
try {
|
|
649
|
+
// Move the uploaded file to the specified path
|
|
478
650
|
await fs.rename(file.path, filePath);
|
|
479
651
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
652
|
+
// Install the plugin package
|
|
480
653
|
if (filename.endsWith('.tgz')) {
|
|
481
654
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
482
655
|
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose']);
|
|
@@ -496,6 +669,7 @@ export class Frontend extends EventEmitter {
|
|
|
496
669
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
497
670
|
}
|
|
498
671
|
});
|
|
672
|
+
// Fallback for routing (must be the last route)
|
|
499
673
|
this.expressApp.use((req, res) => {
|
|
500
674
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
501
675
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -504,13 +678,16 @@ export class Frontend extends EventEmitter {
|
|
|
504
678
|
}
|
|
505
679
|
async stop() {
|
|
506
680
|
this.log.debug('Stopping the frontend...');
|
|
681
|
+
// Remove listeners from the express app
|
|
507
682
|
if (this.expressApp) {
|
|
508
683
|
this.expressApp.removeAllListeners();
|
|
509
684
|
this.expressApp = undefined;
|
|
510
685
|
this.log.debug('Frontend app closed successfully');
|
|
511
686
|
}
|
|
687
|
+
// Close the WebSocket server
|
|
512
688
|
if (this.webSocketServer) {
|
|
513
689
|
this.log.debug('Closing WebSocket server...');
|
|
690
|
+
// Close all active connections
|
|
514
691
|
this.webSocketServer.clients.forEach((client) => {
|
|
515
692
|
if (client.readyState === WebSocket.OPEN) {
|
|
516
693
|
client.close();
|
|
@@ -519,6 +696,7 @@ export class Frontend extends EventEmitter {
|
|
|
519
696
|
await withTimeout(new Promise((resolve) => {
|
|
520
697
|
this.webSocketServer?.close((error) => {
|
|
521
698
|
if (error) {
|
|
699
|
+
// istanbul ignore next
|
|
522
700
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
523
701
|
}
|
|
524
702
|
else {
|
|
@@ -531,11 +709,13 @@ export class Frontend extends EventEmitter {
|
|
|
531
709
|
this.webSocketServer.removeAllListeners();
|
|
532
710
|
this.webSocketServer = undefined;
|
|
533
711
|
}
|
|
712
|
+
// Close the http server
|
|
534
713
|
if (this.httpServer) {
|
|
535
714
|
this.log.debug('Closing http server...');
|
|
536
715
|
await withTimeout(new Promise((resolve) => {
|
|
537
716
|
this.httpServer?.close((error) => {
|
|
538
717
|
if (error) {
|
|
718
|
+
// istanbul ignore next
|
|
539
719
|
this.log.error(`Error closing http server: ${error}`);
|
|
540
720
|
}
|
|
541
721
|
else {
|
|
@@ -549,11 +729,13 @@ export class Frontend extends EventEmitter {
|
|
|
549
729
|
this.httpServer = undefined;
|
|
550
730
|
this.log.debug('Frontend http server closed successfully');
|
|
551
731
|
}
|
|
732
|
+
// Close the https server
|
|
552
733
|
if (this.httpsServer) {
|
|
553
734
|
this.log.debug('Closing https server...');
|
|
554
735
|
await withTimeout(new Promise((resolve) => {
|
|
555
736
|
this.httpsServer?.close((error) => {
|
|
556
737
|
if (error) {
|
|
738
|
+
// istanbul ignore next
|
|
557
739
|
this.log.error(`Error closing https server: ${error}`);
|
|
558
740
|
}
|
|
559
741
|
else {
|
|
@@ -569,6 +751,7 @@ export class Frontend extends EventEmitter {
|
|
|
569
751
|
}
|
|
570
752
|
this.log.debug('Frontend stopped successfully');
|
|
571
753
|
}
|
|
754
|
+
// Function to format bytes to KB, MB, or GB
|
|
572
755
|
formatMemoryUsage = (bytes) => {
|
|
573
756
|
if (bytes >= 1024 ** 3) {
|
|
574
757
|
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -580,6 +763,7 @@ export class Frontend extends EventEmitter {
|
|
|
580
763
|
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
581
764
|
}
|
|
582
765
|
};
|
|
766
|
+
// Function to format system uptime with only the most significant unit
|
|
583
767
|
formatOsUpTime = (seconds) => {
|
|
584
768
|
if (seconds >= 86400) {
|
|
585
769
|
const days = Math.floor(seconds / 86400);
|
|
@@ -595,7 +779,13 @@ export class Frontend extends EventEmitter {
|
|
|
595
779
|
}
|
|
596
780
|
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
597
781
|
};
|
|
782
|
+
/**
|
|
783
|
+
* Retrieves the api settings data.
|
|
784
|
+
*
|
|
785
|
+
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
786
|
+
*/
|
|
598
787
|
async getApiSettings() {
|
|
788
|
+
// Update the system information
|
|
599
789
|
this.matterbridge.systemInformation.totalMemory = this.formatMemoryUsage(os.totalmem());
|
|
600
790
|
this.matterbridge.systemInformation.freeMemory = this.formatMemoryUsage(os.freemem());
|
|
601
791
|
this.matterbridge.systemInformation.systemUptime = this.formatOsUpTime(os.uptime());
|
|
@@ -604,6 +794,7 @@ export class Frontend extends EventEmitter {
|
|
|
604
794
|
this.matterbridge.systemInformation.rss = this.formatMemoryUsage(process.memoryUsage().rss);
|
|
605
795
|
this.matterbridge.systemInformation.heapTotal = this.formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
606
796
|
this.matterbridge.systemInformation.heapUsed = this.formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
797
|
+
// Update the matterbridge information
|
|
607
798
|
this.matterbridge.matterbridgeInformation.bridgeMode = this.matterbridge.bridgeMode;
|
|
608
799
|
this.matterbridge.matterbridgeInformation.restartMode = this.matterbridge.restartMode;
|
|
609
800
|
this.matterbridge.matterbridgeInformation.profile = this.matterbridge.profile;
|
|
@@ -615,6 +806,7 @@ export class Frontend extends EventEmitter {
|
|
|
615
806
|
this.matterbridge.matterbridgeInformation.matterPort = (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
616
807
|
this.matterbridge.matterbridgeInformation.matterDiscriminator = await this.matterbridge.nodeContext?.get('matterdiscriminator');
|
|
617
808
|
this.matterbridge.matterbridgeInformation.matterPasscode = await this.matterbridge.nodeContext?.get('matterpasscode');
|
|
809
|
+
// Update the matterbridge information in bridge mode
|
|
618
810
|
if (this.matterbridge.bridgeMode === 'bridge' && this.matterbridge.serverNode && !this.matterbridge.hasCleanupStarted) {
|
|
619
811
|
this.matterbridge.matterbridgeInformation.matterbridgePaired = this.matterbridge.serverNode.state.commissioning.commissioned;
|
|
620
812
|
this.matterbridge.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : this.matterbridge.serverNode.state.commissioning.pairingCodes.qrPairingCode;
|
|
@@ -624,6 +816,12 @@ export class Frontend extends EventEmitter {
|
|
|
624
816
|
}
|
|
625
817
|
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: this.matterbridge.matterbridgeInformation };
|
|
626
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Retrieves the reachable attribute.
|
|
821
|
+
*
|
|
822
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
823
|
+
* @returns {boolean} The reachable attribute.
|
|
824
|
+
*/
|
|
627
825
|
getReachability(device) {
|
|
628
826
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
629
827
|
return false;
|
|
@@ -635,6 +833,12 @@ export class Frontend extends EventEmitter {
|
|
|
635
833
|
return true;
|
|
636
834
|
return false;
|
|
637
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* Retrieves the power source attribute.
|
|
838
|
+
*
|
|
839
|
+
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
840
|
+
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
841
|
+
*/
|
|
638
842
|
getPowerSource(endpoint) {
|
|
639
843
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
640
844
|
return undefined;
|
|
@@ -650,13 +854,21 @@ export class Frontend extends EventEmitter {
|
|
|
650
854
|
}
|
|
651
855
|
return;
|
|
652
856
|
};
|
|
857
|
+
// Root endpoint
|
|
653
858
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
654
859
|
return powerSource(endpoint);
|
|
860
|
+
// Child endpoints
|
|
655
861
|
for (const child of endpoint.getChildEndpoints()) {
|
|
656
862
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
657
863
|
return powerSource(child);
|
|
658
864
|
}
|
|
659
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Retrieves the commissioned status, matter pairing codes, fabrics and sessions from a given device in server mode.
|
|
868
|
+
*
|
|
869
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the data from.
|
|
870
|
+
* @returns {ApiDevicesMatter | undefined} An ApiDevicesMatter object or undefined if not found.
|
|
871
|
+
*/
|
|
660
872
|
getMatterDataFromDevice(device) {
|
|
661
873
|
if (device.mode === 'server' && device.serverNode) {
|
|
662
874
|
return {
|
|
@@ -668,6 +880,13 @@ export class Frontend extends EventEmitter {
|
|
|
668
880
|
};
|
|
669
881
|
}
|
|
670
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Retrieves the cluster text description from a given device.
|
|
885
|
+
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
886
|
+
*
|
|
887
|
+
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
888
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
889
|
+
*/
|
|
671
890
|
getClusterTextFromDevice(device) {
|
|
672
891
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
673
892
|
return '';
|
|
@@ -678,6 +897,7 @@ export class Frontend extends EventEmitter {
|
|
|
678
897
|
if (composed)
|
|
679
898
|
return 'Composed: ' + composed.value;
|
|
680
899
|
}
|
|
900
|
+
// istanbul ignore next cause is not reachable
|
|
681
901
|
return '';
|
|
682
902
|
};
|
|
683
903
|
const getFixedLabel = (device) => {
|
|
@@ -687,11 +907,13 @@ export class Frontend extends EventEmitter {
|
|
|
687
907
|
if (composed)
|
|
688
908
|
return 'Composed: ' + composed.value;
|
|
689
909
|
}
|
|
910
|
+
// istanbul ignore next cause is not reacheable
|
|
690
911
|
return '';
|
|
691
912
|
};
|
|
692
913
|
let attributes = '';
|
|
693
914
|
let supportedModes = [];
|
|
694
915
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
916
|
+
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
695
917
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
696
918
|
return;
|
|
697
919
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -781,11 +1003,17 @@ export class Frontend extends EventEmitter {
|
|
|
781
1003
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
782
1004
|
attributes += `${getUserLabel(device)} `;
|
|
783
1005
|
});
|
|
1006
|
+
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
784
1007
|
return attributes.trimStart().trimEnd();
|
|
785
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Retrieves the registered plugins sanitized for res.json().
|
|
1011
|
+
*
|
|
1012
|
+
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
1013
|
+
*/
|
|
786
1014
|
getPlugins() {
|
|
787
1015
|
if (this.matterbridge.hasCleanupStarted)
|
|
788
|
-
return [];
|
|
1016
|
+
return []; // Skip if cleanup has started
|
|
789
1017
|
const baseRegisteredPlugins = [];
|
|
790
1018
|
for (const plugin of this.matterbridge.plugins) {
|
|
791
1019
|
baseRegisteredPlugins.push({
|
|
@@ -815,6 +1043,7 @@ export class Frontend extends EventEmitter {
|
|
|
815
1043
|
schemaJson: plugin.schemaJson,
|
|
816
1044
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
817
1045
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1046
|
+
// Childbridge mode specific data
|
|
818
1047
|
paired: plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.commissioned : undefined,
|
|
819
1048
|
qrPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.qrPairingCode : undefined,
|
|
820
1049
|
manualPairingCode: this.matterbridge.matterbridgeInformation.matterbridgeEndAdvertise ? undefined : plugin.serverNode && plugin.serverNode.lifecycle.isOnline ? plugin.serverNode.state.commissioning.pairingCodes.manualPairingCode : undefined,
|
|
@@ -824,13 +1053,21 @@ export class Frontend extends EventEmitter {
|
|
|
824
1053
|
}
|
|
825
1054
|
return baseRegisteredPlugins;
|
|
826
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Retrieves the devices from Matterbridge.
|
|
1058
|
+
*
|
|
1059
|
+
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1060
|
+
* @returns {Promise<ApiDevices[]>} A promise that resolves to an array of ApiDevices for the frontend.
|
|
1061
|
+
*/
|
|
827
1062
|
async getDevices(pluginName) {
|
|
828
1063
|
if (this.matterbridge.hasCleanupStarted)
|
|
829
|
-
return [];
|
|
1064
|
+
return []; // Skip if cleanup has started
|
|
830
1065
|
const devices = [];
|
|
831
1066
|
for (const device of this.matterbridge.devices.array()) {
|
|
1067
|
+
// Filter by pluginName if provided
|
|
832
1068
|
if (pluginName && pluginName !== device.plugin)
|
|
833
1069
|
continue;
|
|
1070
|
+
// Check if the device has the required properties
|
|
834
1071
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
835
1072
|
continue;
|
|
836
1073
|
devices.push({
|
|
@@ -850,22 +1087,37 @@ export class Frontend extends EventEmitter {
|
|
|
850
1087
|
}
|
|
851
1088
|
return devices;
|
|
852
1089
|
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1092
|
+
*
|
|
1093
|
+
* Response for /api/clusters
|
|
1094
|
+
*
|
|
1095
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1096
|
+
* @param {number} endpointNumber - The endpoint number.
|
|
1097
|
+
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1098
|
+
*/
|
|
853
1099
|
getClusters(pluginName, endpointNumber) {
|
|
854
1100
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
855
1101
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
856
1102
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
857
1103
|
return;
|
|
858
1104
|
}
|
|
1105
|
+
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1106
|
+
// Get the device types from the main endpoint
|
|
859
1107
|
const deviceTypes = [];
|
|
860
1108
|
const clusters = [];
|
|
861
1109
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
862
1110
|
deviceTypes.push(d.deviceType);
|
|
863
1111
|
});
|
|
1112
|
+
// Get the clusters from the main endpoint
|
|
864
1113
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
865
1114
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
866
1115
|
return;
|
|
867
1116
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
868
1117
|
return;
|
|
1118
|
+
// console.log(
|
|
1119
|
+
// `${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}`,
|
|
1120
|
+
// );
|
|
869
1121
|
clusters.push({
|
|
870
1122
|
endpoint: endpoint.number.toString(),
|
|
871
1123
|
id: 'main',
|
|
@@ -878,12 +1130,19 @@ export class Frontend extends EventEmitter {
|
|
|
878
1130
|
attributeLocalValue: attributeValue,
|
|
879
1131
|
});
|
|
880
1132
|
});
|
|
1133
|
+
// Get the child endpoints
|
|
881
1134
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1135
|
+
// if (childEndpoints.length === 0) {
|
|
1136
|
+
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1137
|
+
// }
|
|
882
1138
|
childEndpoints.forEach((childEndpoint) => {
|
|
1139
|
+
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
883
1140
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
884
1141
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
885
1142
|
return;
|
|
886
1143
|
}
|
|
1144
|
+
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1145
|
+
// Get the device types of the child endpoint
|
|
887
1146
|
const deviceTypes = [];
|
|
888
1147
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
889
1148
|
deviceTypes.push(d.deviceType);
|
|
@@ -893,9 +1152,12 @@ export class Frontend extends EventEmitter {
|
|
|
893
1152
|
return;
|
|
894
1153
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
895
1154
|
return;
|
|
1155
|
+
// console.log(
|
|
1156
|
+
// `${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}`,
|
|
1157
|
+
// );
|
|
896
1158
|
clusters.push({
|
|
897
1159
|
endpoint: childEndpoint.number.toString(),
|
|
898
|
-
id: childEndpoint.maybeId ?? 'null',
|
|
1160
|
+
id: childEndpoint.maybeId ?? 'null', // Never happens
|
|
899
1161
|
deviceTypes,
|
|
900
1162
|
clusterName: capitalizeFirstLetter(clusterName),
|
|
901
1163
|
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
@@ -908,6 +1170,13 @@ export class Frontend extends EventEmitter {
|
|
|
908
1170
|
});
|
|
909
1171
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
910
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Handles incoming websocket messages for the Matterbridge frontend.
|
|
1175
|
+
*
|
|
1176
|
+
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1177
|
+
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1178
|
+
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1179
|
+
*/
|
|
911
1180
|
async wsMessageHandler(client, message) {
|
|
912
1181
|
let data;
|
|
913
1182
|
try {
|
|
@@ -954,33 +1223,44 @@ export class Frontend extends EventEmitter {
|
|
|
954
1223
|
this.wssSendSnackbarMessage(`Installed package ${data.params.packageName}`, 5, 'success');
|
|
955
1224
|
const packageName = data.params.packageName.replace(/@.*$/, '');
|
|
956
1225
|
if (data.params.restart === false && packageName !== 'matterbridge') {
|
|
1226
|
+
// The install comes from InstallPlugins
|
|
957
1227
|
this.matterbridge.plugins
|
|
958
1228
|
.add(packageName)
|
|
959
1229
|
.then((plugin) => {
|
|
960
1230
|
if (plugin) {
|
|
1231
|
+
// The plugin is not registered
|
|
961
1232
|
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
962
1233
|
this.matterbridge.plugins
|
|
963
1234
|
.load(plugin, true, 'The plugin has been added', true)
|
|
1235
|
+
// eslint-disable-next-line promise/no-nesting
|
|
964
1236
|
.then(() => {
|
|
965
1237
|
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
966
1238
|
this.wssSendRefreshRequired('plugins');
|
|
967
1239
|
return;
|
|
968
1240
|
})
|
|
1241
|
+
// eslint-disable-next-line promise/no-nesting
|
|
969
1242
|
.catch((_error) => {
|
|
1243
|
+
//
|
|
970
1244
|
});
|
|
971
1245
|
}
|
|
972
1246
|
else {
|
|
1247
|
+
// The plugin is already registered
|
|
973
1248
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
974
1249
|
this.wssSendRefreshRequired('plugins');
|
|
975
1250
|
this.wssSendRestartRequired(true, true);
|
|
976
1251
|
}
|
|
977
1252
|
return;
|
|
978
1253
|
})
|
|
1254
|
+
// eslint-disable-next-line promise/no-nesting
|
|
979
1255
|
.catch((_error) => {
|
|
1256
|
+
//
|
|
980
1257
|
});
|
|
981
1258
|
}
|
|
982
1259
|
else {
|
|
1260
|
+
// The package is matterbridge
|
|
1261
|
+
// istanbul ignore next
|
|
983
1262
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1263
|
+
// istanbul ignore next if
|
|
984
1264
|
if (this.matterbridge.restartMode !== '') {
|
|
985
1265
|
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
986
1266
|
this.matterbridge.shutdownProcess();
|
|
@@ -1002,6 +1282,7 @@ export class Frontend extends EventEmitter {
|
|
|
1002
1282
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' }));
|
|
1003
1283
|
return;
|
|
1004
1284
|
}
|
|
1285
|
+
// The package is a plugin
|
|
1005
1286
|
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1006
1287
|
if (plugin) {
|
|
1007
1288
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
@@ -1010,6 +1291,7 @@ export class Frontend extends EventEmitter {
|
|
|
1010
1291
|
this.wssSendRefreshRequired('plugins');
|
|
1011
1292
|
this.wssSendRefreshRequired('devices');
|
|
1012
1293
|
}
|
|
1294
|
+
// Uninstall the package
|
|
1013
1295
|
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1014
1296
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1015
1297
|
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'])
|
|
@@ -1050,6 +1332,7 @@ export class Frontend extends EventEmitter {
|
|
|
1050
1332
|
return;
|
|
1051
1333
|
})
|
|
1052
1334
|
.catch((_error) => {
|
|
1335
|
+
//
|
|
1053
1336
|
});
|
|
1054
1337
|
}
|
|
1055
1338
|
else {
|
|
@@ -1096,6 +1379,7 @@ export class Frontend extends EventEmitter {
|
|
|
1096
1379
|
return;
|
|
1097
1380
|
})
|
|
1098
1381
|
.catch((_error) => {
|
|
1382
|
+
//
|
|
1099
1383
|
});
|
|
1100
1384
|
}
|
|
1101
1385
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1121,6 +1405,7 @@ export class Frontend extends EventEmitter {
|
|
|
1121
1405
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1122
1406
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1123
1407
|
if (plugin.serverNode) {
|
|
1408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1124
1409
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1125
1410
|
plugin.serverNode = undefined;
|
|
1126
1411
|
}
|
|
@@ -1131,15 +1416,16 @@ export class Frontend extends EventEmitter {
|
|
|
1131
1416
|
}
|
|
1132
1417
|
}
|
|
1133
1418
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1134
|
-
plugin.restartRequired = false;
|
|
1419
|
+
plugin.restartRequired = false; // Reset plugin restartRequired
|
|
1135
1420
|
let needRestart = 0;
|
|
1136
1421
|
for (const plugin of this.matterbridge.plugins) {
|
|
1137
1422
|
if (plugin.restartRequired)
|
|
1138
1423
|
needRestart++;
|
|
1139
1424
|
}
|
|
1140
1425
|
if (needRestart === 0) {
|
|
1141
|
-
this.wssSendRestartNotRequired(true);
|
|
1426
|
+
this.wssSendRestartNotRequired(true); // Reset global restart required message
|
|
1142
1427
|
}
|
|
1428
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1143
1429
|
if (plugin.serverNode)
|
|
1144
1430
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1145
1431
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1245,6 +1531,8 @@ export class Frontend extends EventEmitter {
|
|
|
1245
1531
|
else if (data.method === '/api/advertise') {
|
|
1246
1532
|
const pairingCodes = await this.matterbridge.advertiseServerNode(this.matterbridge.serverNode);
|
|
1247
1533
|
this.matterbridge.matterbridgeInformation.matterbridgeAdvertise = true;
|
|
1534
|
+
// this.matterbridge.matterbridgeQrPairingCode = pairingCodes?.qrPairingCode;
|
|
1535
|
+
// this.matterbridge.matterbridgeManualPairingCode = pairingCodes?.manualPairingCode;
|
|
1248
1536
|
this.wssSendRefreshRequired('matterbridgeAdvertise');
|
|
1249
1537
|
this.wssSendSnackbarMessage(`Started fabrics share`, 0);
|
|
1250
1538
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, response: pairingCodes, success: true }));
|
|
@@ -1367,22 +1655,22 @@ export class Frontend extends EventEmitter {
|
|
|
1367
1655
|
if (isValidString(data.params.value, 4)) {
|
|
1368
1656
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1369
1657
|
if (data.params.value === 'Debug') {
|
|
1370
|
-
await this.matterbridge.setLogLevel("debug");
|
|
1658
|
+
await this.matterbridge.setLogLevel("debug" /* LogLevel.DEBUG */);
|
|
1371
1659
|
}
|
|
1372
1660
|
else if (data.params.value === 'Info') {
|
|
1373
|
-
await this.matterbridge.setLogLevel("info");
|
|
1661
|
+
await this.matterbridge.setLogLevel("info" /* LogLevel.INFO */);
|
|
1374
1662
|
}
|
|
1375
1663
|
else if (data.params.value === 'Notice') {
|
|
1376
|
-
await this.matterbridge.setLogLevel("notice");
|
|
1664
|
+
await this.matterbridge.setLogLevel("notice" /* LogLevel.NOTICE */);
|
|
1377
1665
|
}
|
|
1378
1666
|
else if (data.params.value === 'Warn') {
|
|
1379
|
-
await this.matterbridge.setLogLevel("warn");
|
|
1667
|
+
await this.matterbridge.setLogLevel("warn" /* LogLevel.WARN */);
|
|
1380
1668
|
}
|
|
1381
1669
|
else if (data.params.value === 'Error') {
|
|
1382
|
-
await this.matterbridge.setLogLevel("error");
|
|
1670
|
+
await this.matterbridge.setLogLevel("error" /* LogLevel.ERROR */);
|
|
1383
1671
|
}
|
|
1384
1672
|
else if (data.params.value === 'Fatal') {
|
|
1385
|
-
await this.matterbridge.setLogLevel("fatal");
|
|
1673
|
+
await this.matterbridge.setLogLevel("fatal" /* LogLevel.FATAL */);
|
|
1386
1674
|
}
|
|
1387
1675
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1388
1676
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true }));
|
|
@@ -1393,6 +1681,7 @@ export class Frontend extends EventEmitter {
|
|
|
1393
1681
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1394
1682
|
this.matterbridge.matterbridgeInformation.fileLogger = data.params.value;
|
|
1395
1683
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1684
|
+
// Create the file logger for matterbridge
|
|
1396
1685
|
if (data.params.value)
|
|
1397
1686
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, this.matterbridge.matterbridgeLoggerFile), this.matterbridge.matterbridgeInformation.loggerLevel, true);
|
|
1398
1687
|
else
|
|
@@ -1439,6 +1728,7 @@ export class Frontend extends EventEmitter {
|
|
|
1439
1728
|
});
|
|
1440
1729
|
}
|
|
1441
1730
|
catch (error) {
|
|
1731
|
+
/* istanbul ignore next */
|
|
1442
1732
|
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
1733
|
}
|
|
1444
1734
|
}
|
|
@@ -1447,6 +1737,7 @@ export class Frontend extends EventEmitter {
|
|
|
1447
1737
|
Logger.removeLogger('matterfilelogger');
|
|
1448
1738
|
}
|
|
1449
1739
|
catch (error) {
|
|
1740
|
+
/* istanbul ignore next */
|
|
1450
1741
|
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
1742
|
}
|
|
1452
1743
|
}
|
|
@@ -1559,15 +1850,19 @@ export class Frontend extends EventEmitter {
|
|
|
1559
1850
|
return;
|
|
1560
1851
|
}
|
|
1561
1852
|
const config = plugin.configJson;
|
|
1853
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1562
1854
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1855
|
+
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1563
1856
|
if (select === 'serial')
|
|
1564
1857
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1565
1858
|
if (select === 'name')
|
|
1566
1859
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1567
1860
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1861
|
+
// Remove postfix from the serial if it exists
|
|
1568
1862
|
if (config.postfix) {
|
|
1569
1863
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1570
1864
|
}
|
|
1865
|
+
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1571
1866
|
if (isValidArray(config.whiteList, 1)) {
|
|
1572
1867
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1573
1868
|
config.whiteList.push(data.params.serial);
|
|
@@ -1576,6 +1871,7 @@ export class Frontend extends EventEmitter {
|
|
|
1576
1871
|
config.whiteList.push(data.params.name);
|
|
1577
1872
|
}
|
|
1578
1873
|
}
|
|
1874
|
+
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1579
1875
|
if (isValidArray(config.blackList, 1)) {
|
|
1580
1876
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1581
1877
|
config.blackList = config.blackList.filter((item) => item !== data.params.serial);
|
|
@@ -1606,7 +1902,9 @@ export class Frontend extends EventEmitter {
|
|
|
1606
1902
|
return;
|
|
1607
1903
|
}
|
|
1608
1904
|
const config = plugin.configJson;
|
|
1905
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1609
1906
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1907
|
+
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1610
1908
|
if (select === 'serial')
|
|
1611
1909
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1612
1910
|
if (select === 'name')
|
|
@@ -1615,6 +1913,7 @@ export class Frontend extends EventEmitter {
|
|
|
1615
1913
|
if (config.postfix) {
|
|
1616
1914
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1617
1915
|
}
|
|
1916
|
+
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1618
1917
|
if (isValidArray(config.whiteList, 1)) {
|
|
1619
1918
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1620
1919
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
|
|
@@ -1623,6 +1922,7 @@ export class Frontend extends EventEmitter {
|
|
|
1623
1922
|
config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
|
|
1624
1923
|
}
|
|
1625
1924
|
}
|
|
1925
|
+
// Add the serial to the blackList
|
|
1626
1926
|
if (isValidArray(config.blackList)) {
|
|
1627
1927
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1628
1928
|
config.blackList.push(data.params.serial);
|
|
@@ -1656,126 +1956,251 @@ export class Frontend extends EventEmitter {
|
|
|
1656
1956
|
this.log.error(`Error parsing message "${message}" from websocket client:`, error instanceof Error ? error.message : error);
|
|
1657
1957
|
}
|
|
1658
1958
|
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1961
|
+
*
|
|
1962
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1963
|
+
* @param {string} time - The time string of the message
|
|
1964
|
+
* @param {string} name - The logger name of the message
|
|
1965
|
+
* @param {string} message - The content of the message.
|
|
1966
|
+
*
|
|
1967
|
+
* @remarks
|
|
1968
|
+
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1969
|
+
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1970
|
+
* The function sends the message to all connected clients.
|
|
1971
|
+
*/
|
|
1659
1972
|
wssSendMessage(level, time, name, message) {
|
|
1660
1973
|
if (!level || !time || !name || !message)
|
|
1661
1974
|
return;
|
|
1975
|
+
// Remove ANSI escape codes from the message
|
|
1976
|
+
// eslint-disable-next-line no-control-regex
|
|
1662
1977
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1978
|
+
// Remove leading asterisks from the message
|
|
1663
1979
|
message = message.replace(/^\*+/, '');
|
|
1980
|
+
// Replace all occurrences of \t and \n
|
|
1664
1981
|
message = message.replace(/[\t\n]/g, '');
|
|
1982
|
+
// Remove non-printable characters
|
|
1983
|
+
// eslint-disable-next-line no-control-regex
|
|
1665
1984
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
1985
|
+
// Replace all occurrences of \" with "
|
|
1666
1986
|
message = message.replace(/\\"/g, '"');
|
|
1987
|
+
// Replace all occurrences of angle-brackets with < and >"
|
|
1667
1988
|
message = message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1989
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
1668
1990
|
const maxContinuousLength = 100;
|
|
1669
1991
|
const keepStartLength = 20;
|
|
1670
1992
|
const keepEndLength = 20;
|
|
1993
|
+
// Split the message into words
|
|
1671
1994
|
message = message
|
|
1672
1995
|
.split(' ')
|
|
1673
1996
|
.map((word) => {
|
|
1997
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
1674
1998
|
if (word.length > maxContinuousLength) {
|
|
1675
1999
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
1676
2000
|
}
|
|
1677
2001
|
return word;
|
|
1678
2002
|
})
|
|
1679
2003
|
.join(' ');
|
|
2004
|
+
// Send the message to all connected clients
|
|
1680
2005
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1681
2006
|
if (client.readyState === WebSocket.OPEN) {
|
|
1682
2007
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
1683
2008
|
}
|
|
1684
2009
|
});
|
|
1685
2010
|
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2013
|
+
*
|
|
2014
|
+
* @param {string} changed - The changed value. If null, the whole page will be refreshed.
|
|
2015
|
+
* possible values:
|
|
2016
|
+
* - 'matterbridgeLatestVersion'
|
|
2017
|
+
* - 'matterbridgeDevVersion'
|
|
2018
|
+
* - 'matterbridgeAdvertise'
|
|
2019
|
+
* - 'online'
|
|
2020
|
+
* - 'offline'
|
|
2021
|
+
* - 'reachability'
|
|
2022
|
+
* - 'settings'
|
|
2023
|
+
* - 'plugins'
|
|
2024
|
+
* - 'pluginsRestart'
|
|
2025
|
+
* - 'devices'
|
|
2026
|
+
* - 'fabrics'
|
|
2027
|
+
* - 'sessions'
|
|
2028
|
+
*/
|
|
1686
2029
|
wssSendRefreshRequired(changed = null) {
|
|
1687
2030
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2031
|
+
// Send the message to all connected clients
|
|
1688
2032
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1689
2033
|
if (client.readyState === WebSocket.OPEN) {
|
|
1690
2034
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: { changed: changed } }));
|
|
1691
2035
|
}
|
|
1692
2036
|
});
|
|
1693
2037
|
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2040
|
+
*
|
|
2041
|
+
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2042
|
+
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2043
|
+
*/
|
|
1694
2044
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
1695
2045
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
1696
2046
|
this.matterbridge.matterbridgeInformation.restartRequired = true;
|
|
1697
2047
|
this.matterbridge.matterbridgeInformation.fixedRestartRequired = fixed;
|
|
1698
2048
|
if (snackbar === true)
|
|
1699
2049
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2050
|
+
// Send the message to all connected clients
|
|
1700
2051
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1701
2052
|
if (client.readyState === WebSocket.OPEN) {
|
|
1702
2053
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: { fixed } }));
|
|
1703
2054
|
}
|
|
1704
2055
|
});
|
|
1705
2056
|
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2059
|
+
*
|
|
2060
|
+
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients.
|
|
2061
|
+
*/
|
|
1706
2062
|
wssSendRestartNotRequired(snackbar = true) {
|
|
1707
2063
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
1708
2064
|
this.matterbridge.matterbridgeInformation.restartRequired = false;
|
|
1709
2065
|
if (snackbar === true)
|
|
1710
2066
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2067
|
+
// Send the message to all connected clients
|
|
1711
2068
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1712
2069
|
if (client.readyState === WebSocket.OPEN) {
|
|
1713
2070
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', params: {} }));
|
|
1714
2071
|
}
|
|
1715
2072
|
});
|
|
1716
2073
|
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Sends a need to update WebSocket message to all connected clients.
|
|
2076
|
+
*
|
|
2077
|
+
* @param {boolean} devVersion - If true, the update is for a development version.
|
|
2078
|
+
*/
|
|
1717
2079
|
wssSendUpdateRequired(devVersion = false) {
|
|
1718
2080
|
this.log.debug('Sending an update required message to all connected clients');
|
|
1719
2081
|
this.matterbridge.matterbridgeInformation.updateRequired = true;
|
|
2082
|
+
// Send the message to all connected clients
|
|
1720
2083
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1721
2084
|
if (client.readyState === WebSocket.OPEN) {
|
|
1722
2085
|
client.send(JSON.stringify({ id: WS_ID_UPDATE_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: devVersion ? 'update_required_dev' : 'update_required', params: {} }));
|
|
1723
2086
|
}
|
|
1724
2087
|
});
|
|
1725
2088
|
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Sends a cpu update message to all connected clients.
|
|
2091
|
+
*
|
|
2092
|
+
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2093
|
+
*/
|
|
1726
2094
|
wssSendCpuUpdate(cpuUsage) {
|
|
1727
2095
|
if (hasParameter('debug'))
|
|
1728
2096
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2097
|
+
// Send the message to all connected clients
|
|
1729
2098
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1730
2099
|
if (client.readyState === WebSocket.OPEN) {
|
|
1731
2100
|
client.send(JSON.stringify({ id: WS_ID_CPU_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', params: { cpuUsage } }));
|
|
1732
2101
|
}
|
|
1733
2102
|
});
|
|
1734
2103
|
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Sends a memory update message to all connected clients.
|
|
2106
|
+
*
|
|
2107
|
+
* @param {string} totalMemory - The total memory in bytes.
|
|
2108
|
+
* @param {string} freeMemory - The free memory in bytes.
|
|
2109
|
+
* @param {string} rss - The resident set size in bytes.
|
|
2110
|
+
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2111
|
+
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2112
|
+
* @param {string} external - The external memory in bytes.
|
|
2113
|
+
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2114
|
+
*/
|
|
1735
2115
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
1736
2116
|
if (hasParameter('debug'))
|
|
1737
2117
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2118
|
+
// Send the message to all connected clients
|
|
1738
2119
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1739
2120
|
if (client.readyState === WebSocket.OPEN) {
|
|
1740
2121
|
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
2122
|
}
|
|
1742
2123
|
});
|
|
1743
2124
|
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Sends an uptime update message to all connected clients.
|
|
2127
|
+
*
|
|
2128
|
+
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2129
|
+
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2130
|
+
*/
|
|
1744
2131
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
1745
2132
|
if (hasParameter('debug'))
|
|
1746
2133
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2134
|
+
// Send the message to all connected clients
|
|
1747
2135
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1748
2136
|
if (client.readyState === WebSocket.OPEN) {
|
|
1749
2137
|
client.send(JSON.stringify({ id: WS_ID_UPTIME_UPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', params: { systemUptime, processUptime } }));
|
|
1750
2138
|
}
|
|
1751
2139
|
});
|
|
1752
2140
|
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Sends an open snackbar message to all connected clients.
|
|
2143
|
+
*
|
|
2144
|
+
* @param {string} message - The message to send.
|
|
2145
|
+
* @param {number} timeout - The timeout in seconds for the snackbar message.
|
|
2146
|
+
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the snackbar message (default info).
|
|
2147
|
+
*/
|
|
1753
2148
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
1754
2149
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2150
|
+
// Send the message to all connected clients
|
|
1755
2151
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1756
2152
|
if (client.readyState === WebSocket.OPEN) {
|
|
1757
2153
|
client.send(JSON.stringify({ id: WS_ID_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message, timeout, severity } }));
|
|
1758
2154
|
}
|
|
1759
2155
|
});
|
|
1760
2156
|
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Sends a close snackbar message to all connected clients.
|
|
2159
|
+
*
|
|
2160
|
+
* @param {string} message - The message to send.
|
|
2161
|
+
*/
|
|
1761
2162
|
wssSendCloseSnackbarMessage(message) {
|
|
1762
2163
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2164
|
+
// Send the message to all connected clients
|
|
1763
2165
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1764
2166
|
if (client.readyState === WebSocket.OPEN) {
|
|
1765
2167
|
client.send(JSON.stringify({ id: WS_ID_CLOSE_SNACKBAR, src: 'Matterbridge', dst: 'Frontend', params: { message } }));
|
|
1766
2168
|
}
|
|
1767
2169
|
});
|
|
1768
2170
|
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Sends an attribute update message to all connected WebSocket clients.
|
|
2173
|
+
*
|
|
2174
|
+
* @param {string | undefined} plugin - The name of the plugin.
|
|
2175
|
+
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2176
|
+
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2177
|
+
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2178
|
+
* @param {string} attribute - The name of the attribute that changed.
|
|
2179
|
+
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2180
|
+
*
|
|
2181
|
+
* @remarks
|
|
2182
|
+
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2183
|
+
* with the updated attribute information.
|
|
2184
|
+
*/
|
|
1769
2185
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, cluster, attribute, value) {
|
|
1770
2186
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2187
|
+
// Send the message to all connected clients
|
|
1771
2188
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1772
2189
|
if (client.readyState === WebSocket.OPEN) {
|
|
1773
2190
|
client.send(JSON.stringify({ id: WS_ID_STATEUPDATE, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', params: { plugin, serialNumber, uniqueId, cluster, attribute, value } }));
|
|
1774
2191
|
}
|
|
1775
2192
|
});
|
|
1776
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Sends a message to all connected clients.
|
|
2196
|
+
*
|
|
2197
|
+
* @param {number} id - The message id.
|
|
2198
|
+
* @param {string} method - The message method.
|
|
2199
|
+
* @param {Record<string, string | number | boolean>} params - The message parameters.
|
|
2200
|
+
*/
|
|
1777
2201
|
wssBroadcastMessage(id, method, params) {
|
|
1778
2202
|
this.log.debug(`Sending a broadcast message id ${id} method ${method} params ${debugStringify(params ?? {})} to all connected clients`);
|
|
2203
|
+
// Send the message to all connected clients
|
|
1779
2204
|
this.webSocketServer?.clients.forEach((client) => {
|
|
1780
2205
|
if (client.readyState === WebSocket.OPEN) {
|
|
1781
2206
|
client.send(JSON.stringify({ id, src: 'Matterbridge', dst: 'Frontend', method, params }));
|
|
@@ -1783,3 +2208,4 @@ export class Frontend extends EventEmitter {
|
|
|
1783
2208
|
});
|
|
1784
2209
|
}
|
|
1785
2210
|
}
|
|
2211
|
+
//# sourceMappingURL=frontend.js.map
|