matterbridge 3.3.0 → 3.3.1-dev-20251007-4e5eaac
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 +29 -3
- package/README.md +9 -1
- package/dist/broadcastServer.js +73 -0
- package/dist/broadcastServerTypes.js +1 -0
- package/dist/cli.js +2 -91
- package/dist/cliEmitter.js +0 -30
- package/dist/clusters/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -24
- package/dist/deviceManager.js +44 -98
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.js +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +0 -42
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.js +3 -25
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/coap.js +13 -126
- package/dist/dgram/dgram.js +2 -114
- package/dist/dgram/mb_coap.js +3 -41
- package/dist/dgram/mb_mdns.js +15 -80
- package/dist/dgram/mdns.js +137 -299
- package/dist/dgram/multicast.js +1 -62
- package/dist/dgram/unicast.js +0 -54
- package/dist/frontend.js +172 -604
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +4 -57
- package/dist/index.js +0 -41
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -3
- package/dist/matter/types.js +0 -3
- package/dist/matterbridge.js +148 -877
- package/dist/matterbridgeAccessoryPlatform.js +0 -36
- package/dist/matterbridgeBehaviors.js +5 -65
- package/dist/matterbridgeDeviceTypes.js +17 -630
- package/dist/matterbridgeDynamicPlatform.js +0 -36
- package/dist/matterbridgeEndpoint.js +58 -1398
- package/dist/matterbridgeEndpointHelpers.js +12 -345
- package/dist/matterbridgePlatform.js +2 -341
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +133 -254
- package/dist/shelly.js +11 -172
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -71
- package/dist/utils/colorUtils.js +2 -97
- package/dist/utils/commandLine.js +0 -54
- package/dist/utils/copyDirectory.js +1 -38
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/error.js +0 -41
- package/dist/utils/export.js +0 -1
- package/dist/utils/hex.js +0 -124
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/jestHelpers.js +3 -153
- package/dist/utils/network.js +76 -129
- package/dist/utils/spawn.js +5 -75
- package/dist/utils/wait.js +8 -60
- package/frontend/build/assets/index.js +4 -7
- package/frontend/build/assets/vendor_mui.js +1 -1
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +44 -44
- package/package.json +2 -3
- package/dist/cli.d.ts +0 -26
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts +0 -34
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/clusters/export.d.ts +0 -2
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -28
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -112
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts +0 -98
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts +0 -48
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/cooktop.d.ts +0 -60
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts +0 -71
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts +0 -75
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts +0 -17
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts +0 -46
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts +0 -47
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts +0 -67
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts +0 -81
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts +0 -168
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts +0 -105
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts +0 -118
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts +0 -40
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts +0 -87
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts +0 -166
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts +0 -111
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/coap.d.ts +0 -205
- package/dist/dgram/coap.d.ts.map +0 -1
- package/dist/dgram/coap.js.map +0 -1
- package/dist/dgram/dgram.d.ts +0 -141
- package/dist/dgram/dgram.d.ts.map +0 -1
- package/dist/dgram/dgram.js.map +0 -1
- package/dist/dgram/mb_coap.d.ts +0 -24
- package/dist/dgram/mb_coap.d.ts.map +0 -1
- package/dist/dgram/mb_coap.js.map +0 -1
- package/dist/dgram/mb_mdns.d.ts +0 -24
- package/dist/dgram/mb_mdns.d.ts.map +0 -1
- package/dist/dgram/mb_mdns.js.map +0 -1
- package/dist/dgram/mdns.d.ts +0 -290
- package/dist/dgram/mdns.d.ts.map +0 -1
- package/dist/dgram/mdns.js.map +0 -1
- package/dist/dgram/multicast.d.ts +0 -67
- package/dist/dgram/multicast.d.ts.map +0 -1
- package/dist/dgram/multicast.js.map +0 -1
- package/dist/dgram/unicast.d.ts +0 -56
- package/dist/dgram/unicast.d.ts.map +0 -1
- package/dist/dgram/unicast.js.map +0 -1
- package/dist/frontend.d.ts +0 -232
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts +0 -514
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/globalMatterbridge.d.ts +0 -59
- package/dist/globalMatterbridge.d.ts.map +0 -1
- package/dist/globalMatterbridge.js +0 -70
- package/dist/globalMatterbridge.js.map +0 -1
- package/dist/helpers.d.ts +0 -48
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -33
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -430
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -1747
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -761
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1534
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -407
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -402
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -201
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -270
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts +0 -174
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -75
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -99
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -59
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -33
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createDirectory.d.ts +0 -34
- package/dist/utils/createDirectory.d.ts.map +0 -1
- package/dist/utils/createDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -39
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -32
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -54
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/error.d.ts +0 -44
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/error.js.map +0 -1
- package/dist/utils/export.d.ts +0 -13
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -89
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -103
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/jestHelpers.d.ts +0 -137
- package/dist/utils/jestHelpers.d.ts.map +0 -1
- package/dist/utils/jestHelpers.js.map +0 -1
- package/dist/utils/network.d.ts +0 -84
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -34
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -54
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
package/dist/frontend.js
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
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.3.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
|
|
25
1
|
import { createServer } from 'node:http';
|
|
26
2
|
import https from 'node:https';
|
|
27
3
|
import os from 'node:os';
|
|
@@ -29,22 +5,20 @@ import path from 'node:path';
|
|
|
29
5
|
import { existsSync, promises as fs, unlinkSync } from 'node:fs';
|
|
30
6
|
import EventEmitter from 'node:events';
|
|
31
7
|
import { appendFile } from 'node:fs/promises';
|
|
32
|
-
// Third-party modules
|
|
33
8
|
import express from 'express';
|
|
34
9
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
35
10
|
import multer from 'multer';
|
|
36
|
-
|
|
37
|
-
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
38
|
-
// @matter
|
|
11
|
+
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
|
|
39
12
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
|
|
40
13
|
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
41
14
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
|
|
42
15
|
import { CommissioningOptions } from '@matter/main/types';
|
|
43
|
-
|
|
16
|
+
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
|
|
44
17
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
|
|
45
|
-
import {
|
|
18
|
+
import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
|
|
46
19
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
47
20
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
21
|
+
import { BroadcastServer } from './broadcastServer.js';
|
|
48
22
|
export class Frontend extends EventEmitter {
|
|
49
23
|
matterbridge;
|
|
50
24
|
log;
|
|
@@ -54,11 +28,55 @@ export class Frontend extends EventEmitter {
|
|
|
54
28
|
httpServer;
|
|
55
29
|
httpsServer;
|
|
56
30
|
webSocketServer;
|
|
31
|
+
server;
|
|
57
32
|
constructor(matterbridge) {
|
|
58
33
|
super();
|
|
59
34
|
this.matterbridge = matterbridge;
|
|
60
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
35
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
61
36
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
37
|
+
this.server = new BroadcastServer('plugins', this.log);
|
|
38
|
+
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
39
|
+
}
|
|
40
|
+
destroy() {
|
|
41
|
+
this.server.close();
|
|
42
|
+
}
|
|
43
|
+
async msgHandler(msg) {
|
|
44
|
+
if (this.server.isWorkerRequest(msg, msg.type)) {
|
|
45
|
+
this.log.debug(`**Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
46
|
+
switch (msg.type) {
|
|
47
|
+
default:
|
|
48
|
+
this.log.warn(`Unknown broadcast request ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (this.server.isWorkerResponse(msg, msg.type)) {
|
|
52
|
+
this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
53
|
+
switch (msg.type) {
|
|
54
|
+
case 'plugins_install':
|
|
55
|
+
this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
|
|
56
|
+
if (msg.response.success) {
|
|
57
|
+
this.wssSendRestartRequired(true, true);
|
|
58
|
+
this.wssSendRefreshRequired('plugins');
|
|
59
|
+
this.wssSendSnackbarMessage(`Installed package ${msg.response.packageName}`, 5, 'success');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not installed`, 10, 'error');
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 'plugins_uninstall':
|
|
66
|
+
this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.response.packageName}...`);
|
|
67
|
+
if (msg.response.success) {
|
|
68
|
+
this.wssSendRestartRequired(true, true);
|
|
69
|
+
this.wssSendRefreshRequired('plugins');
|
|
70
|
+
this.wssSendSnackbarMessage(`Uninstalled package ${msg.response.packageName}`, 5, 'success');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not uninstalled`, 10, 'error');
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
this.log.warn(`Unknown broadcast response ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
62
80
|
}
|
|
63
81
|
set logLevel(logLevel) {
|
|
64
82
|
this.log.logLevel = logLevel;
|
|
@@ -66,51 +84,11 @@ export class Frontend extends EventEmitter {
|
|
|
66
84
|
async start(port = 8283) {
|
|
67
85
|
this.port = port;
|
|
68
86
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${this.port}${db}`);
|
|
69
|
-
|
|
70
|
-
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads'); // Is created by matterbridge initialize
|
|
87
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
71
88
|
const upload = multer({ dest: uploadDir });
|
|
72
|
-
// Create the express app that serves the frontend
|
|
73
89
|
this.expressApp = express();
|
|
74
|
-
// Inject logging/debug wrapper for route/middleware registration
|
|
75
|
-
/*
|
|
76
|
-
const methods = ['get', 'post', 'put', 'delete', 'use'];
|
|
77
|
-
for (const method of methods) {
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
-
const original = (this.expressApp as any)[method].bind(this.expressApp);
|
|
80
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
-
(this.expressApp as any)[method] = (path: any, ...rest: any) => {
|
|
82
|
-
try {
|
|
83
|
-
console.log(`[DEBUG] Registering ${method.toUpperCase()} route:`, path);
|
|
84
|
-
return original(path, ...rest);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.error(`[ERROR] Failed to register route: ${path}`);
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
*/
|
|
92
|
-
// Log all requests to the server for debugging
|
|
93
|
-
/*
|
|
94
|
-
this.expressApp.use((req, res, next) => {
|
|
95
|
-
this.log.debug(`***Received request on expressApp: ${req.method} ${req.url}`);
|
|
96
|
-
next();
|
|
97
|
-
});
|
|
98
|
-
*/
|
|
99
|
-
// Serve static files from '/static' endpoint
|
|
100
90
|
this.expressApp.use(express.static(path.join(this.matterbridge.rootDirectory, 'frontend/build')));
|
|
101
|
-
// Read the package.json file to get the frontend version
|
|
102
|
-
try {
|
|
103
|
-
this.log.debug(`Reading frontend package.json...`);
|
|
104
|
-
const frontendJson = await fs.readFile(path.join(this.matterbridge.rootDirectory, 'frontend/package.json'), 'utf8');
|
|
105
|
-
this.matterbridge.matterbridgeInformation.frontendVersion = JSON.parse(frontendJson)?.version;
|
|
106
|
-
this.log.debug(`Frontend version ${CYAN}${this.matterbridge.matterbridgeInformation.frontendVersion}${db}`);
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
// istanbul ignore next
|
|
110
|
-
this.log.error(`Failed to read frontend package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
|
-
}
|
|
112
91
|
if (!hasParameter('ssl')) {
|
|
113
|
-
// Create an HTTP server and attach the express app
|
|
114
92
|
try {
|
|
115
93
|
this.log.debug(`Creating HTTP server...`);
|
|
116
94
|
this.httpServer = createServer(this.expressApp);
|
|
@@ -120,7 +98,6 @@ export class Frontend extends EventEmitter {
|
|
|
120
98
|
this.emit('server_error', error);
|
|
121
99
|
return;
|
|
122
100
|
}
|
|
123
|
-
// Listen on the specified port
|
|
124
101
|
if (hasParameter('ingress')) {
|
|
125
102
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
126
103
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -161,7 +138,6 @@ export class Frontend extends EventEmitter {
|
|
|
161
138
|
let passphrase;
|
|
162
139
|
let httpsServerOptions = {};
|
|
163
140
|
if (existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
164
|
-
// Load the p12 certificate and the passphrase
|
|
165
141
|
try {
|
|
166
142
|
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
167
143
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
@@ -173,7 +149,7 @@ export class Frontend extends EventEmitter {
|
|
|
173
149
|
}
|
|
174
150
|
try {
|
|
175
151
|
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
176
|
-
passphrase = passphrase.trim();
|
|
152
|
+
passphrase = passphrase.trim();
|
|
177
153
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
178
154
|
}
|
|
179
155
|
catch (error) {
|
|
@@ -184,7 +160,6 @@ export class Frontend extends EventEmitter {
|
|
|
184
160
|
httpsServerOptions = { pfx, passphrase };
|
|
185
161
|
}
|
|
186
162
|
else {
|
|
187
|
-
// 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.
|
|
188
163
|
try {
|
|
189
164
|
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
190
165
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
@@ -214,10 +189,9 @@ export class Frontend extends EventEmitter {
|
|
|
214
189
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
215
190
|
}
|
|
216
191
|
if (hasParameter('mtls')) {
|
|
217
|
-
httpsServerOptions.requestCert = true;
|
|
218
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
192
|
+
httpsServerOptions.requestCert = true;
|
|
193
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
219
194
|
}
|
|
220
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
221
195
|
try {
|
|
222
196
|
this.log.debug(`Creating HTTPS server...`);
|
|
223
197
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -227,7 +201,6 @@ export class Frontend extends EventEmitter {
|
|
|
227
201
|
this.emit('server_error', error);
|
|
228
202
|
return;
|
|
229
203
|
}
|
|
230
|
-
// Listen on the specified port
|
|
231
204
|
if (hasParameter('ingress')) {
|
|
232
205
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
233
206
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -259,19 +232,15 @@ export class Frontend extends EventEmitter {
|
|
|
259
232
|
return;
|
|
260
233
|
});
|
|
261
234
|
}
|
|
262
|
-
|
|
263
|
-
const wssPort = this.port;
|
|
264
|
-
const wssHost = hasParameter('ssl') ? `wss://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.matterbridge.systemInformation.ipv4Address}:${wssPort}`;
|
|
265
|
-
this.log.debug(`Creating WebSocketServer on host ${CYAN}${wssHost}${db}...`);
|
|
235
|
+
this.log.debug(`Creating WebSocketServer...`);
|
|
266
236
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
267
237
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
268
238
|
const clientIp = request.socket.remoteAddress;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
239
|
+
let callbackLogLevel = "notice";
|
|
240
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
241
|
+
callbackLogLevel = "info";
|
|
242
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
243
|
+
callbackLogLevel = "debug";
|
|
275
244
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
276
245
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
277
246
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -293,7 +262,6 @@ export class Frontend extends EventEmitter {
|
|
|
293
262
|
}
|
|
294
263
|
});
|
|
295
264
|
ws.on('error', (error) => {
|
|
296
|
-
// istanbul ignore next
|
|
297
265
|
this.log.error(`WebSocket client error: ${error}`);
|
|
298
266
|
});
|
|
299
267
|
});
|
|
@@ -301,13 +269,12 @@ export class Frontend extends EventEmitter {
|
|
|
301
269
|
this.log.debug(`WebSocketServer closed`);
|
|
302
270
|
});
|
|
303
271
|
this.webSocketServer.on('listening', () => {
|
|
304
|
-
this.log.info(`The WebSocketServer is listening
|
|
305
|
-
this.emit('websocket_server_listening',
|
|
272
|
+
this.log.info(`The WebSocketServer is listening`);
|
|
273
|
+
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
306
274
|
});
|
|
307
275
|
this.webSocketServer.on('error', (ws, error) => {
|
|
308
276
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
309
277
|
});
|
|
310
|
-
// Subscribe to cli events
|
|
311
278
|
cliEmitter.removeAllListeners();
|
|
312
279
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
313
280
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -318,8 +285,6 @@ export class Frontend extends EventEmitter {
|
|
|
318
285
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
319
286
|
this.wssSendCpuUpdate(cpuUsage);
|
|
320
287
|
});
|
|
321
|
-
// Endpoint to validate login code
|
|
322
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
323
288
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
324
289
|
const { password } = req.body;
|
|
325
290
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -338,48 +303,41 @@ export class Frontend extends EventEmitter {
|
|
|
338
303
|
this.log.warn('/api/login error wrong password');
|
|
339
304
|
res.json({ valid: false });
|
|
340
305
|
}
|
|
341
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
342
306
|
}
|
|
343
307
|
catch (error) {
|
|
344
308
|
this.log.error('/api/login error getting password');
|
|
345
309
|
res.json({ valid: false });
|
|
346
310
|
}
|
|
347
311
|
});
|
|
348
|
-
// Endpoint to provide health check for docker
|
|
349
312
|
this.expressApp.get('/health', (req, res) => {
|
|
350
313
|
this.log.debug('Express received /health');
|
|
351
314
|
const healthStatus = {
|
|
352
|
-
status: 'ok',
|
|
353
|
-
uptime: process.uptime(),
|
|
354
|
-
timestamp: new Date().toISOString(),
|
|
315
|
+
status: 'ok',
|
|
316
|
+
uptime: process.uptime(),
|
|
317
|
+
timestamp: new Date().toISOString(),
|
|
355
318
|
};
|
|
356
319
|
res.status(200).json(healthStatus);
|
|
357
320
|
});
|
|
358
|
-
// Endpoint to provide memory usage details
|
|
359
321
|
this.expressApp.get('/memory', async (req, res) => {
|
|
360
322
|
this.log.debug('Express received /memory');
|
|
361
|
-
// Memory usage from process
|
|
362
323
|
const memoryUsageRaw = process.memoryUsage();
|
|
363
324
|
const memoryUsage = {
|
|
364
|
-
rss:
|
|
365
|
-
heapTotal:
|
|
366
|
-
heapUsed:
|
|
367
|
-
external:
|
|
368
|
-
arrayBuffers:
|
|
325
|
+
rss: formatMemoryUsage(memoryUsageRaw.rss),
|
|
326
|
+
heapTotal: formatMemoryUsage(memoryUsageRaw.heapTotal),
|
|
327
|
+
heapUsed: formatMemoryUsage(memoryUsageRaw.heapUsed),
|
|
328
|
+
external: formatMemoryUsage(memoryUsageRaw.external),
|
|
329
|
+
arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
369
330
|
};
|
|
370
|
-
// V8 heap statistics
|
|
371
331
|
const { default: v8 } = await import('node:v8');
|
|
372
332
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
373
333
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
374
|
-
|
|
375
|
-
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
376
|
-
// Format heapSpaces
|
|
334
|
+
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
|
|
377
335
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
378
336
|
...space,
|
|
379
|
-
space_size:
|
|
380
|
-
space_used_size:
|
|
381
|
-
space_available_size:
|
|
382
|
-
physical_space_size:
|
|
337
|
+
space_size: formatMemoryUsage(space.space_size),
|
|
338
|
+
space_used_size: formatMemoryUsage(space.space_used_size),
|
|
339
|
+
space_available_size: formatMemoryUsage(space.space_available_size),
|
|
340
|
+
physical_space_size: formatMemoryUsage(space.physical_space_size),
|
|
383
341
|
}));
|
|
384
342
|
const { createRequire } = await import('node:module');
|
|
385
343
|
const require = createRequire(import.meta.url);
|
|
@@ -392,23 +350,18 @@ export class Frontend extends EventEmitter {
|
|
|
392
350
|
};
|
|
393
351
|
res.status(200).json(memoryReport);
|
|
394
352
|
});
|
|
395
|
-
// Endpoint to provide settings
|
|
396
353
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
397
354
|
this.log.debug('The frontend sent /api/settings');
|
|
398
355
|
res.json(await this.getApiSettings());
|
|
399
356
|
});
|
|
400
|
-
// Endpoint to provide plugins
|
|
401
357
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
402
358
|
this.log.debug('The frontend sent /api/plugins');
|
|
403
|
-
res.json(this.getPlugins());
|
|
359
|
+
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
404
360
|
});
|
|
405
|
-
// Endpoint to provide devices
|
|
406
361
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
407
362
|
this.log.debug('The frontend sent /api/devices');
|
|
408
|
-
|
|
409
|
-
res.json(devices);
|
|
363
|
+
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
410
364
|
});
|
|
411
|
-
// Endpoint to view the matterbridge log
|
|
412
365
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
413
366
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
414
367
|
try {
|
|
@@ -421,7 +374,6 @@ export class Frontend extends EventEmitter {
|
|
|
421
374
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
422
375
|
}
|
|
423
376
|
});
|
|
424
|
-
// Endpoint to view the matter.js log
|
|
425
377
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
426
378
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
427
379
|
try {
|
|
@@ -434,11 +386,9 @@ export class Frontend extends EventEmitter {
|
|
|
434
386
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
435
387
|
}
|
|
436
388
|
});
|
|
437
|
-
// Endpoint to view the diagnostic.log
|
|
438
389
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
439
390
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
440
391
|
const serverNodes = [];
|
|
441
|
-
// istanbul ignore else
|
|
442
392
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
443
393
|
if (this.matterbridge.serverNode)
|
|
444
394
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -449,7 +399,6 @@ export class Frontend extends EventEmitter {
|
|
|
449
399
|
serverNodes.push(plugin.serverNode);
|
|
450
400
|
}
|
|
451
401
|
}
|
|
452
|
-
// istanbul ignore next
|
|
453
402
|
for (const device of this.matterbridge.devices.array()) {
|
|
454
403
|
if (device.serverNode)
|
|
455
404
|
serverNodes.push(device.serverNode);
|
|
@@ -472,20 +421,17 @@ export class Frontend extends EventEmitter {
|
|
|
472
421
|
values: [...serverNodes],
|
|
473
422
|
})));
|
|
474
423
|
delete Logger.destinations.diagnostic;
|
|
475
|
-
await wait(500);
|
|
424
|
+
await wait(500);
|
|
476
425
|
try {
|
|
477
426
|
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
|
|
478
427
|
res.type('text/plain');
|
|
479
428
|
res.send(data.slice(29));
|
|
480
429
|
}
|
|
481
430
|
catch (error) {
|
|
482
|
-
// istanbul ignore next
|
|
483
431
|
this.log.error(`Error reading diagnostic log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
484
|
-
// istanbul ignore next
|
|
485
432
|
res.status(500).send('Error reading diagnostic log file.');
|
|
486
433
|
}
|
|
487
434
|
});
|
|
488
|
-
// Endpoint to view the shelly log
|
|
489
435
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
490
436
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
491
437
|
try {
|
|
@@ -498,7 +444,6 @@ export class Frontend extends EventEmitter {
|
|
|
498
444
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
499
445
|
}
|
|
500
446
|
});
|
|
501
|
-
// Endpoint to download the matterbridge log
|
|
502
447
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
503
448
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
504
449
|
try {
|
|
@@ -512,14 +457,12 @@ export class Frontend extends EventEmitter {
|
|
|
512
457
|
}
|
|
513
458
|
res.type('text/plain');
|
|
514
459
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
515
|
-
/* istanbul ignore if */
|
|
516
460
|
if (error) {
|
|
517
461
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
518
462
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
519
463
|
}
|
|
520
464
|
});
|
|
521
465
|
});
|
|
522
|
-
// Endpoint to download the matter log
|
|
523
466
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
524
467
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
525
468
|
try {
|
|
@@ -533,14 +476,12 @@ export class Frontend extends EventEmitter {
|
|
|
533
476
|
}
|
|
534
477
|
res.type('text/plain');
|
|
535
478
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
536
|
-
/* istanbul ignore if */
|
|
537
479
|
if (error) {
|
|
538
480
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
539
481
|
res.status(500).send('Error downloading the matter log file');
|
|
540
482
|
}
|
|
541
483
|
});
|
|
542
484
|
});
|
|
543
|
-
// Endpoint to download the shelly log
|
|
544
485
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
545
486
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
546
487
|
try {
|
|
@@ -554,93 +495,77 @@ export class Frontend extends EventEmitter {
|
|
|
554
495
|
}
|
|
555
496
|
res.type('text/plain');
|
|
556
497
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
557
|
-
/* istanbul ignore if */
|
|
558
498
|
if (error) {
|
|
559
499
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
560
500
|
res.status(500).send('Error downloading Shelly system log file');
|
|
561
501
|
}
|
|
562
502
|
});
|
|
563
503
|
});
|
|
564
|
-
// Endpoint to download the matterbridge storage directory
|
|
565
504
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
566
505
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
567
506
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
568
507
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
569
|
-
/* istanbul ignore if */
|
|
570
508
|
if (error) {
|
|
571
509
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
572
510
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
573
511
|
}
|
|
574
512
|
});
|
|
575
513
|
});
|
|
576
|
-
// Endpoint to download the matter storage file
|
|
577
514
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
578
515
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
579
516
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
580
517
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
581
|
-
/* istanbul ignore if */
|
|
582
518
|
if (error) {
|
|
583
519
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
584
520
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
585
521
|
}
|
|
586
522
|
});
|
|
587
523
|
});
|
|
588
|
-
// Endpoint to download the matterbridge plugin directory
|
|
589
524
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
590
525
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
591
526
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
592
527
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
593
|
-
/* istanbul ignore if */
|
|
594
528
|
if (error) {
|
|
595
529
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
596
530
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
597
531
|
}
|
|
598
532
|
});
|
|
599
533
|
});
|
|
600
|
-
// Endpoint to download the matterbridge plugin config files
|
|
601
534
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
602
535
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
603
536
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
604
537
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
605
|
-
/* istanbul ignore if */
|
|
606
538
|
if (error) {
|
|
607
539
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
608
540
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
609
541
|
}
|
|
610
542
|
});
|
|
611
543
|
});
|
|
612
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
613
544
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
614
545
|
this.log.debug('The frontend sent /api/download-backup');
|
|
615
546
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
616
|
-
/* istanbul ignore if */
|
|
617
547
|
if (error) {
|
|
618
548
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
619
549
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
620
550
|
}
|
|
621
551
|
});
|
|
622
552
|
});
|
|
623
|
-
// Endpoint to upload a package
|
|
624
553
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
625
554
|
const { filename } = req.body;
|
|
626
555
|
const file = req.file;
|
|
627
|
-
/* istanbul ignore if */
|
|
628
556
|
if (!file || !filename) {
|
|
629
557
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
630
558
|
res.status(400).send('Invalid request: file and filename are required');
|
|
631
559
|
return;
|
|
632
560
|
}
|
|
633
561
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
634
|
-
// Define the path where the plugin file will be saved
|
|
635
562
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
636
563
|
try {
|
|
637
|
-
// Move the uploaded file to the specified path
|
|
638
564
|
await fs.rename(file.path, filePath);
|
|
639
565
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
640
|
-
// Install the plugin package
|
|
641
566
|
if (filename.endsWith('.tgz')) {
|
|
642
567
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
643
|
-
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
|
|
568
|
+
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
644
569
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
645
570
|
this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
|
|
646
571
|
this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
|
|
@@ -657,7 +582,6 @@ export class Frontend extends EventEmitter {
|
|
|
657
582
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
658
583
|
}
|
|
659
584
|
});
|
|
660
|
-
// Fallback for routing (must be the last route)
|
|
661
585
|
this.expressApp.use((req, res) => {
|
|
662
586
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
663
587
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -666,16 +590,13 @@ export class Frontend extends EventEmitter {
|
|
|
666
590
|
}
|
|
667
591
|
async stop() {
|
|
668
592
|
this.log.debug('Stopping the frontend...');
|
|
669
|
-
// Remove listeners from the express app
|
|
670
593
|
if (this.expressApp) {
|
|
671
594
|
this.expressApp.removeAllListeners();
|
|
672
595
|
this.expressApp = undefined;
|
|
673
596
|
this.log.debug('Frontend app closed successfully');
|
|
674
597
|
}
|
|
675
|
-
// Close the WebSocket server
|
|
676
598
|
if (this.webSocketServer) {
|
|
677
599
|
this.log.debug('Closing WebSocket server...');
|
|
678
|
-
// Close all active connections
|
|
679
600
|
this.webSocketServer.clients.forEach((client) => {
|
|
680
601
|
if (client.readyState === WebSocket.OPEN) {
|
|
681
602
|
client.close();
|
|
@@ -684,7 +605,6 @@ export class Frontend extends EventEmitter {
|
|
|
684
605
|
await withTimeout(new Promise((resolve) => {
|
|
685
606
|
this.webSocketServer?.close((error) => {
|
|
686
607
|
if (error) {
|
|
687
|
-
// istanbul ignore next
|
|
688
608
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
689
609
|
}
|
|
690
610
|
else {
|
|
@@ -697,27 +617,8 @@ export class Frontend extends EventEmitter {
|
|
|
697
617
|
this.webSocketServer.removeAllListeners();
|
|
698
618
|
this.webSocketServer = undefined;
|
|
699
619
|
}
|
|
700
|
-
// Close the http server
|
|
701
620
|
if (this.httpServer) {
|
|
702
621
|
this.log.debug('Closing http server...');
|
|
703
|
-
/*
|
|
704
|
-
await withTimeout(
|
|
705
|
-
new Promise<void>((resolve) => {
|
|
706
|
-
this.httpServer?.close((error) => {
|
|
707
|
-
if (error) {
|
|
708
|
-
// istanbul ignore next
|
|
709
|
-
this.log.error(`Error closing http server: ${error}`);
|
|
710
|
-
} else {
|
|
711
|
-
this.log.debug('Http server closed successfully');
|
|
712
|
-
this.emit('server_stopped');
|
|
713
|
-
}
|
|
714
|
-
resolve();
|
|
715
|
-
});
|
|
716
|
-
}),
|
|
717
|
-
5000,
|
|
718
|
-
false,
|
|
719
|
-
);
|
|
720
|
-
*/
|
|
721
622
|
this.httpServer.close();
|
|
722
623
|
this.log.debug('Http server closed successfully');
|
|
723
624
|
this.listening = false;
|
|
@@ -726,27 +627,8 @@ export class Frontend extends EventEmitter {
|
|
|
726
627
|
this.httpServer = undefined;
|
|
727
628
|
this.log.debug('Frontend http server closed successfully');
|
|
728
629
|
}
|
|
729
|
-
// Close the https server
|
|
730
630
|
if (this.httpsServer) {
|
|
731
631
|
this.log.debug('Closing https server...');
|
|
732
|
-
/*
|
|
733
|
-
await withTimeout(
|
|
734
|
-
new Promise<void>((resolve) => {
|
|
735
|
-
this.httpsServer?.close((error) => {
|
|
736
|
-
if (error) {
|
|
737
|
-
// istanbul ignore next
|
|
738
|
-
this.log.error(`Error closing https server: ${error}`);
|
|
739
|
-
} else {
|
|
740
|
-
this.log.debug('Https server closed successfully');
|
|
741
|
-
this.emit('server_stopped');
|
|
742
|
-
}
|
|
743
|
-
resolve();
|
|
744
|
-
});
|
|
745
|
-
}),
|
|
746
|
-
5000,
|
|
747
|
-
false,
|
|
748
|
-
);
|
|
749
|
-
*/
|
|
750
632
|
this.httpsServer.close();
|
|
751
633
|
this.log.debug('Https server closed successfully');
|
|
752
634
|
this.listening = false;
|
|
@@ -757,69 +639,50 @@ export class Frontend extends EventEmitter {
|
|
|
757
639
|
}
|
|
758
640
|
this.log.debug('Frontend stopped successfully');
|
|
759
641
|
}
|
|
760
|
-
// Function to format bytes to KB, MB, or GB
|
|
761
|
-
formatMemoryUsage = (bytes) => {
|
|
762
|
-
if (bytes >= 1024 ** 3) {
|
|
763
|
-
return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
|
|
764
|
-
}
|
|
765
|
-
else if (bytes >= 1024 ** 2) {
|
|
766
|
-
return `${(bytes / 1024 ** 2).toFixed(2)} MB`;
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
// Function to format system uptime with only the most significant unit
|
|
773
|
-
formatOsUpTime = (seconds) => {
|
|
774
|
-
if (seconds >= 86400) {
|
|
775
|
-
const days = Math.floor(seconds / 86400);
|
|
776
|
-
return `${days} day${days !== 1 ? 's' : ''}`;
|
|
777
|
-
}
|
|
778
|
-
if (seconds >= 3600) {
|
|
779
|
-
const hours = Math.floor(seconds / 3600);
|
|
780
|
-
return `${hours} hour${hours !== 1 ? 's' : ''}`;
|
|
781
|
-
}
|
|
782
|
-
if (seconds >= 60) {
|
|
783
|
-
const minutes = Math.floor(seconds / 60);
|
|
784
|
-
return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
|
785
|
-
}
|
|
786
|
-
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
787
|
-
};
|
|
788
|
-
/**
|
|
789
|
-
* Retrieves the api settings data.
|
|
790
|
-
*
|
|
791
|
-
* @returns {Promise<{ matterbridgeInformation: MatterbridgeInformation, systemInformation: SystemInformation }>} A promise that resolve in the api settings object.
|
|
792
|
-
*/
|
|
793
642
|
async getApiSettings() {
|
|
794
|
-
|
|
795
|
-
this.matterbridge.systemInformation.
|
|
796
|
-
this.matterbridge.systemInformation.
|
|
797
|
-
this.matterbridge.systemInformation.
|
|
798
|
-
this.matterbridge.systemInformation.processUptime = this.formatOsUpTime(Math.floor(process.uptime()));
|
|
643
|
+
this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
644
|
+
this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
645
|
+
this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
646
|
+
this.matterbridge.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
|
|
799
647
|
this.matterbridge.systemInformation.cpuUsage = lastCpuUsage.toFixed(2) + ' %';
|
|
800
|
-
this.matterbridge.systemInformation.rss =
|
|
801
|
-
this.matterbridge.systemInformation.heapTotal =
|
|
802
|
-
this.matterbridge.systemInformation.heapUsed =
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
648
|
+
this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
649
|
+
this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
650
|
+
this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
651
|
+
const info = {
|
|
652
|
+
homeDirectory: this.matterbridge.homeDirectory,
|
|
653
|
+
rootDirectory: this.matterbridge.rootDirectory,
|
|
654
|
+
matterbridgeDirectory: this.matterbridge.matterbridgeDirectory,
|
|
655
|
+
matterbridgePluginDirectory: this.matterbridge.matterbridgePluginDirectory,
|
|
656
|
+
matterbridgeCertDirectory: this.matterbridge.matterbridgeCertDirectory,
|
|
657
|
+
globalModulesDirectory: this.matterbridge.globalModulesDirectory,
|
|
658
|
+
matterbridgeVersion: this.matterbridge.matterbridgeVersion,
|
|
659
|
+
matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
|
|
660
|
+
matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
|
|
661
|
+
frontendVersion: this.matterbridge.frontendVersion,
|
|
662
|
+
bridgeMode: this.matterbridge.bridgeMode,
|
|
663
|
+
restartMode: this.matterbridge.restartMode,
|
|
664
|
+
virtualMode: this.matterbridge.virtualMode,
|
|
665
|
+
profile: this.matterbridge.profile,
|
|
666
|
+
readOnly: this.matterbridge.readOnly,
|
|
667
|
+
shellyBoard: this.matterbridge.shellyBoard,
|
|
668
|
+
shellySysUpdate: this.matterbridge.shellySysUpdate,
|
|
669
|
+
shellyMainUpdate: this.matterbridge.shellyMainUpdate,
|
|
670
|
+
loggerLevel: await this.matterbridge.getLogLevel(),
|
|
671
|
+
fileLogger: this.matterbridge.fileLogger,
|
|
672
|
+
matterLoggerLevel: Logger.level,
|
|
673
|
+
matterFileLogger: this.matterbridge.matterFileLogger,
|
|
674
|
+
matterMdnsInterface: this.matterbridge.mdnsInterface,
|
|
675
|
+
matterIpv4Address: this.matterbridge.ipv4Address,
|
|
676
|
+
matterIpv6Address: this.matterbridge.ipv6Address,
|
|
677
|
+
matterPort: (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540,
|
|
678
|
+
matterDiscriminator: await this.matterbridge.nodeContext?.get('matterdiscriminator'),
|
|
679
|
+
matterPasscode: await this.matterbridge.nodeContext?.get('matterpasscode'),
|
|
680
|
+
restartRequired: this.matterbridge.restartRequired,
|
|
681
|
+
fixedRestartRequired: this.matterbridge.fixedRestartRequired,
|
|
682
|
+
updateRequired: this.matterbridge.updateRequired,
|
|
683
|
+
};
|
|
684
|
+
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
816
685
|
}
|
|
817
|
-
/**
|
|
818
|
-
* Retrieves the reachable attribute.
|
|
819
|
-
*
|
|
820
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
821
|
-
* @returns {boolean} The reachable attribute.
|
|
822
|
-
*/
|
|
823
686
|
getReachability(device) {
|
|
824
687
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
825
688
|
return false;
|
|
@@ -831,12 +694,6 @@ export class Frontend extends EventEmitter {
|
|
|
831
694
|
return true;
|
|
832
695
|
return false;
|
|
833
696
|
}
|
|
834
|
-
/**
|
|
835
|
-
* Retrieves the power source attribute.
|
|
836
|
-
*
|
|
837
|
-
* @param {MatterbridgeEndpoint} endpoint - The MatterbridgeDevice to retrieve the power source from.
|
|
838
|
-
* @returns {'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined} The power source attribute.
|
|
839
|
-
*/
|
|
840
697
|
getPowerSource(endpoint) {
|
|
841
698
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
842
699
|
return undefined;
|
|
@@ -852,22 +709,13 @@ export class Frontend extends EventEmitter {
|
|
|
852
709
|
}
|
|
853
710
|
return;
|
|
854
711
|
};
|
|
855
|
-
// Root endpoint
|
|
856
712
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
857
713
|
return powerSource(endpoint);
|
|
858
|
-
// Child endpoints
|
|
859
714
|
for (const child of endpoint.getChildEndpoints()) {
|
|
860
715
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
861
716
|
return powerSource(child);
|
|
862
717
|
}
|
|
863
718
|
}
|
|
864
|
-
/**
|
|
865
|
-
* Retrieves the cluster text description from a given device.
|
|
866
|
-
* The output is a string with the attributes description of the cluster servers in the device to show in the frontend.
|
|
867
|
-
*
|
|
868
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint to retrieve the cluster text from.
|
|
869
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
870
|
-
*/
|
|
871
719
|
getClusterTextFromDevice(device) {
|
|
872
720
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
873
721
|
return '';
|
|
@@ -878,7 +726,6 @@ export class Frontend extends EventEmitter {
|
|
|
878
726
|
if (composed)
|
|
879
727
|
return 'Composed: ' + composed.value;
|
|
880
728
|
}
|
|
881
|
-
// istanbul ignore next cause is not reachable
|
|
882
729
|
return '';
|
|
883
730
|
};
|
|
884
731
|
const getFixedLabel = (device) => {
|
|
@@ -888,13 +735,11 @@ export class Frontend extends EventEmitter {
|
|
|
888
735
|
if (composed)
|
|
889
736
|
return 'Composed: ' + composed.value;
|
|
890
737
|
}
|
|
891
|
-
// istanbul ignore next cause is not reacheable
|
|
892
738
|
return '';
|
|
893
739
|
};
|
|
894
740
|
let attributes = '';
|
|
895
741
|
let supportedModes = [];
|
|
896
742
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
897
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
898
743
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
899
744
|
return;
|
|
900
745
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -984,20 +829,12 @@ export class Frontend extends EventEmitter {
|
|
|
984
829
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
985
830
|
attributes += `${getUserLabel(device)} `;
|
|
986
831
|
});
|
|
987
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
988
832
|
return attributes.trimStart().trimEnd();
|
|
989
833
|
}
|
|
990
|
-
/**
|
|
991
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
992
|
-
*
|
|
993
|
-
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
994
|
-
*/
|
|
995
834
|
getPlugins() {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
for (const plugin of this.matterbridge.plugins) {
|
|
1000
|
-
baseRegisteredPlugins.push({
|
|
835
|
+
const plugins = [];
|
|
836
|
+
for (const plugin of this.matterbridge.plugins.array()) {
|
|
837
|
+
plugins.push({
|
|
1001
838
|
path: plugin.path,
|
|
1002
839
|
type: plugin.type,
|
|
1003
840
|
name: plugin.name,
|
|
@@ -1022,27 +859,16 @@ export class Frontend extends EventEmitter {
|
|
|
1022
859
|
schemaJson: plugin.schemaJson,
|
|
1023
860
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1024
861
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1025
|
-
// Childbridge mode specific data
|
|
1026
862
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1027
863
|
});
|
|
1028
864
|
}
|
|
1029
|
-
return
|
|
865
|
+
return plugins;
|
|
1030
866
|
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Retrieves the devices from Matterbridge.
|
|
1033
|
-
*
|
|
1034
|
-
* @param {string} [pluginName] - The name of the plugin to filter devices by.
|
|
1035
|
-
* @returns {ApiDevices[]} An array of ApiDevices for the frontend.
|
|
1036
|
-
*/
|
|
1037
867
|
getDevices(pluginName) {
|
|
1038
|
-
if (this.matterbridge.hasCleanupStarted)
|
|
1039
|
-
return []; // Skip if cleanup has started
|
|
1040
868
|
const devices = [];
|
|
1041
869
|
for (const device of this.matterbridge.devices.array()) {
|
|
1042
|
-
// Filter by pluginName if provided
|
|
1043
870
|
if (pluginName && pluginName !== device.plugin)
|
|
1044
871
|
continue;
|
|
1045
|
-
// Check if the device has the required properties
|
|
1046
872
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1047
873
|
continue;
|
|
1048
874
|
devices.push({
|
|
@@ -1062,39 +888,24 @@ export class Frontend extends EventEmitter {
|
|
|
1062
888
|
}
|
|
1063
889
|
return devices;
|
|
1064
890
|
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Retrieves the clusters from a given plugin and endpoint number.
|
|
1067
|
-
*
|
|
1068
|
-
* Response for /api/clusters
|
|
1069
|
-
*
|
|
1070
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1071
|
-
* @param {number} endpointNumber - The endpoint number.
|
|
1072
|
-
* @returns {ApiClustersResponse | undefined} A promise that resolves to the clusters or undefined if not found.
|
|
1073
|
-
*/
|
|
1074
891
|
getClusters(pluginName, endpointNumber) {
|
|
1075
892
|
if (this.matterbridge.hasCleanupStarted)
|
|
1076
|
-
return;
|
|
893
|
+
return;
|
|
1077
894
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1078
895
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1079
896
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1080
897
|
return;
|
|
1081
898
|
}
|
|
1082
|
-
// this.log.debug(`***getClusters: getting clusters for device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${endpointNumber}`);
|
|
1083
|
-
// Get the device types from the main endpoint
|
|
1084
899
|
const deviceTypes = [];
|
|
1085
900
|
const clusters = [];
|
|
1086
901
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1087
902
|
deviceTypes.push(d.deviceType);
|
|
1088
903
|
});
|
|
1089
|
-
// Get the clusters from the main endpoint
|
|
1090
904
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1091
905
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1092
906
|
return;
|
|
1093
907
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1094
908
|
return;
|
|
1095
|
-
// console.log(
|
|
1096
|
-
// `${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}`,
|
|
1097
|
-
// );
|
|
1098
909
|
clusters.push({
|
|
1099
910
|
endpoint: endpoint.number.toString(),
|
|
1100
911
|
number: endpoint.number,
|
|
@@ -1108,19 +919,12 @@ export class Frontend extends EventEmitter {
|
|
|
1108
919
|
attributeLocalValue: attributeValue,
|
|
1109
920
|
});
|
|
1110
921
|
});
|
|
1111
|
-
// Get the child endpoints
|
|
1112
922
|
const childEndpoints = endpoint.getChildEndpoints();
|
|
1113
|
-
// if (childEndpoints.length === 0) {
|
|
1114
|
-
// this.log.debug(`***getClusters: found ${childEndpoints.length} child endpoints for device ${endpoint.deviceName} plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1115
|
-
// }
|
|
1116
923
|
childEndpoints.forEach((childEndpoint) => {
|
|
1117
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1118
924
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1119
925
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1120
926
|
return;
|
|
1121
927
|
}
|
|
1122
|
-
// this.log.debug(`***getClusters: getting clusters for child endpoint ${childEndpoint.id} of device ${endpoint.deviceName} plugin ${pluginName} endpoint number ${childEndpoint.number}`);
|
|
1123
|
-
// Get the device types of the child endpoint
|
|
1124
928
|
const deviceTypes = [];
|
|
1125
929
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1126
930
|
deviceTypes.push(d.deviceType);
|
|
@@ -1130,9 +934,6 @@ export class Frontend extends EventEmitter {
|
|
|
1130
934
|
return;
|
|
1131
935
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1132
936
|
return;
|
|
1133
|
-
// console.log(
|
|
1134
|
-
// `${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}`,
|
|
1135
|
-
// );
|
|
1136
937
|
clusters.push({
|
|
1137
938
|
endpoint: childEndpoint.number.toString(),
|
|
1138
939
|
number: childEndpoint.number,
|
|
@@ -1149,13 +950,6 @@ export class Frontend extends EventEmitter {
|
|
|
1149
950
|
});
|
|
1150
951
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
|
|
1151
952
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Handles incoming websocket api request messages from the Matterbridge frontend.
|
|
1154
|
-
*
|
|
1155
|
-
* @param {WebSocket} client - The websocket client that sent the message.
|
|
1156
|
-
* @param {WebSocket.RawData} message - The raw data of the message received from the client.
|
|
1157
|
-
* @returns {Promise<void>} A promise that resolves when the message has been handled.
|
|
1158
|
-
*/
|
|
1159
953
|
async wsMessageHandler(client, message) {
|
|
1160
954
|
let data;
|
|
1161
955
|
const sendResponse = (data) => {
|
|
@@ -1175,7 +969,7 @@ export class Frontend extends EventEmitter {
|
|
|
1175
969
|
};
|
|
1176
970
|
try {
|
|
1177
971
|
data = JSON.parse(message.toString());
|
|
1178
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
972
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1179
973
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1180
974
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1181
975
|
return;
|
|
@@ -1204,111 +998,24 @@ export class Frontend extends EventEmitter {
|
|
|
1204
998
|
}
|
|
1205
999
|
}
|
|
1206
1000
|
else if (data.method === '/api/install') {
|
|
1207
|
-
|
|
1208
|
-
|
|
1001
|
+
if (isValidString(data.params.packageName, 14) && isValidBoolean(data.params.restart)) {
|
|
1002
|
+
this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
|
|
1003
|
+
this.server.request({ type: 'plugins_install', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
|
|
1004
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1209
1007
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/install' });
|
|
1210
|
-
return;
|
|
1211
1008
|
}
|
|
1212
|
-
this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
|
|
1213
|
-
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1214
|
-
spawnCommand(this.matterbridge, 'npm', ['install', '-g', data.params.packageName, '--omit=dev', '--verbose'], data.params.packageName)
|
|
1215
|
-
.then((_response) => {
|
|
1216
|
-
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1217
|
-
this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
|
|
1218
|
-
this.wssSendSnackbarMessage(`Installed package ${localData.params.packageName}`, 5, 'success');
|
|
1219
|
-
const packageName = localData.params.packageName.replace(/@.*$/, '');
|
|
1220
|
-
if (localData.params.restart === false && packageName !== 'matterbridge') {
|
|
1221
|
-
// The install comes from InstallPlugins
|
|
1222
|
-
this.matterbridge.plugins
|
|
1223
|
-
.add(packageName)
|
|
1224
|
-
.then((plugin) => {
|
|
1225
|
-
// istanbul ignore next if
|
|
1226
|
-
if (plugin) {
|
|
1227
|
-
// The plugin is not registered
|
|
1228
|
-
this.wssSendSnackbarMessage(`Added plugin ${packageName}`, 5, 'success');
|
|
1229
|
-
// In childbridge mode the plugins server node is not started when added
|
|
1230
|
-
if (this.matterbridge.bridgeMode === 'childbridge')
|
|
1231
|
-
this.wssSendRestartRequired(true, true);
|
|
1232
|
-
this.matterbridge.plugins
|
|
1233
|
-
.load(plugin, true, 'The plugin has been added', true)
|
|
1234
|
-
// eslint-disable-next-line promise/no-nesting
|
|
1235
|
-
.then(() => {
|
|
1236
|
-
this.wssSendSnackbarMessage(`Started plugin ${packageName}`, 5, 'success');
|
|
1237
|
-
this.wssSendRefreshRequired('plugins');
|
|
1238
|
-
this.wssSendRefreshRequired('devices');
|
|
1239
|
-
return;
|
|
1240
|
-
})
|
|
1241
|
-
// eslint-disable-next-line promise/no-nesting
|
|
1242
|
-
.catch((_error) => {
|
|
1243
|
-
//
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
else {
|
|
1247
|
-
// The plugin is already registered
|
|
1248
|
-
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1249
|
-
this.wssSendRefreshRequired('plugins');
|
|
1250
|
-
this.wssSendRestartRequired(true, true);
|
|
1251
|
-
}
|
|
1252
|
-
return;
|
|
1253
|
-
})
|
|
1254
|
-
// eslint-disable-next-line promise/no-nesting
|
|
1255
|
-
.catch((_error) => {
|
|
1256
|
-
//
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
|
-
else {
|
|
1260
|
-
// The package is matterbridge
|
|
1261
|
-
// istanbul ignore next
|
|
1262
|
-
this.matterbridge.matterbridgeInformation.fixedRestartRequired = true;
|
|
1263
|
-
// istanbul ignore next if
|
|
1264
|
-
if (this.matterbridge.restartMode !== '') {
|
|
1265
|
-
this.wssSendSnackbarMessage(`Restarting matterbridge...`, 0);
|
|
1266
|
-
this.matterbridge.shutdownProcess();
|
|
1267
|
-
}
|
|
1268
|
-
else {
|
|
1269
|
-
this.wssSendRestartRequired(true, true);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
return;
|
|
1273
|
-
})
|
|
1274
|
-
.catch((error) => {
|
|
1275
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error });
|
|
1276
|
-
this.wssSendCloseSnackbarMessage(`Installing package ${localData.params.packageName}...`);
|
|
1277
|
-
this.wssSendSnackbarMessage(`Package ${localData.params.packageName} not installed`, 10, 'error');
|
|
1278
|
-
});
|
|
1279
1009
|
}
|
|
1280
1010
|
else if (data.method === '/api/uninstall') {
|
|
1281
|
-
|
|
1282
|
-
|
|
1011
|
+
if (isValidString(data.params.packageName, 14)) {
|
|
1012
|
+
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1013
|
+
this.server.request({ type: 'plugins_uninstall', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
|
|
1014
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1283
1017
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
|
|
1284
|
-
return;
|
|
1285
1018
|
}
|
|
1286
|
-
// The package is a plugin
|
|
1287
|
-
const plugin = this.matterbridge.plugins.get(data.params.packageName);
|
|
1288
|
-
// istanbul ignore next if
|
|
1289
|
-
if (plugin) {
|
|
1290
|
-
await this.matterbridge.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
1291
|
-
await this.matterbridge.plugins.remove(data.params.packageName);
|
|
1292
|
-
this.wssSendSnackbarMessage(`Removed plugin ${data.params.packageName}`, 5, 'success');
|
|
1293
|
-
this.wssSendRefreshRequired('plugins');
|
|
1294
|
-
this.wssSendRefreshRequired('devices');
|
|
1295
|
-
}
|
|
1296
|
-
// Uninstall the package
|
|
1297
|
-
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1298
|
-
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1299
|
-
spawnCommand(this.matterbridge, 'npm', ['uninstall', '-g', data.params.packageName, '--verbose'], data.params.packageName)
|
|
1300
|
-
.then((_response) => {
|
|
1301
|
-
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1302
|
-
this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
|
|
1303
|
-
this.wssSendSnackbarMessage(`Uninstalled package ${localData.params.packageName}`, 5, 'success');
|
|
1304
|
-
return;
|
|
1305
|
-
})
|
|
1306
|
-
.catch((error) => {
|
|
1307
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: error instanceof Error ? error.message : error });
|
|
1308
|
-
this.wssSendCloseSnackbarMessage(`Uninstalling package ${localData.params.packageName}...`);
|
|
1309
|
-
this.wssSendSnackbarMessage(`Package ${localData.params.packageName} not uninstalled`, 10, 'error');
|
|
1310
|
-
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
1311
|
-
});
|
|
1312
1019
|
}
|
|
1313
1020
|
else if (data.method === '/api/addplugin') {
|
|
1314
1021
|
const localData = data;
|
|
@@ -1336,7 +1043,6 @@ export class Frontend extends EventEmitter {
|
|
|
1336
1043
|
return;
|
|
1337
1044
|
})
|
|
1338
1045
|
.catch((_error) => {
|
|
1339
|
-
//
|
|
1340
1046
|
});
|
|
1341
1047
|
}
|
|
1342
1048
|
else {
|
|
@@ -1384,7 +1090,6 @@ export class Frontend extends EventEmitter {
|
|
|
1384
1090
|
return;
|
|
1385
1091
|
})
|
|
1386
1092
|
.catch((_error) => {
|
|
1387
|
-
//
|
|
1388
1093
|
});
|
|
1389
1094
|
}
|
|
1390
1095
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1410,7 +1115,6 @@ export class Frontend extends EventEmitter {
|
|
|
1410
1115
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1411
1116
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1412
1117
|
if (plugin.serverNode) {
|
|
1413
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1414
1118
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1415
1119
|
plugin.serverNode = undefined;
|
|
1416
1120
|
}
|
|
@@ -1420,20 +1124,18 @@ export class Frontend extends EventEmitter {
|
|
|
1420
1124
|
this.matterbridge.devices.remove(device);
|
|
1421
1125
|
}
|
|
1422
1126
|
}
|
|
1423
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1424
1127
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1425
1128
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1426
1129
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1427
|
-
plugin.restartRequired = false;
|
|
1130
|
+
plugin.restartRequired = false;
|
|
1428
1131
|
let needRestart = 0;
|
|
1429
1132
|
for (const plugin of this.matterbridge.plugins) {
|
|
1430
1133
|
if (plugin.restartRequired)
|
|
1431
1134
|
needRestart++;
|
|
1432
1135
|
}
|
|
1433
1136
|
if (needRestart === 0) {
|
|
1434
|
-
this.wssSendRestartNotRequired(true);
|
|
1137
|
+
this.wssSendRestartNotRequired(true);
|
|
1435
1138
|
}
|
|
1436
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1437
1139
|
if (plugin.serverNode)
|
|
1438
1140
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1439
1141
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1586,11 +1288,11 @@ export class Frontend extends EventEmitter {
|
|
|
1586
1288
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: await this.getApiSettings() });
|
|
1587
1289
|
}
|
|
1588
1290
|
else if (data.method === '/api/plugins') {
|
|
1589
|
-
const plugins = this.getPlugins();
|
|
1291
|
+
const plugins = this.matterbridge.hasCleanupStarted ? [] : this.getPlugins();
|
|
1590
1292
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
|
|
1591
1293
|
}
|
|
1592
1294
|
else if (data.method === '/api/devices') {
|
|
1593
|
-
const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
1295
|
+
const devices = this.matterbridge.hasCleanupStarted ? [] : this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
1594
1296
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
|
|
1595
1297
|
}
|
|
1596
1298
|
else if (data.method === '/api/clusters') {
|
|
@@ -1690,22 +1392,22 @@ export class Frontend extends EventEmitter {
|
|
|
1690
1392
|
if (isValidString(data.params.value, 4)) {
|
|
1691
1393
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1692
1394
|
if (data.params.value === 'Debug') {
|
|
1693
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1395
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1694
1396
|
}
|
|
1695
1397
|
else if (data.params.value === 'Info') {
|
|
1696
|
-
await this.matterbridge.setLogLevel("info"
|
|
1398
|
+
await this.matterbridge.setLogLevel("info");
|
|
1697
1399
|
}
|
|
1698
1400
|
else if (data.params.value === 'Notice') {
|
|
1699
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1401
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1700
1402
|
}
|
|
1701
1403
|
else if (data.params.value === 'Warn') {
|
|
1702
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1404
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1703
1405
|
}
|
|
1704
1406
|
else if (data.params.value === 'Error') {
|
|
1705
|
-
await this.matterbridge.setLogLevel("error"
|
|
1407
|
+
await this.matterbridge.setLogLevel("error");
|
|
1706
1408
|
}
|
|
1707
1409
|
else if (data.params.value === 'Fatal') {
|
|
1708
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1410
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1709
1411
|
}
|
|
1710
1412
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1711
1413
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1714,11 +1416,10 @@ export class Frontend extends EventEmitter {
|
|
|
1714
1416
|
case 'setmblogfile':
|
|
1715
1417
|
if (isValidBoolean(data.params.value)) {
|
|
1716
1418
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1717
|
-
this.matterbridge.
|
|
1419
|
+
this.matterbridge.fileLogger = data.params.value;
|
|
1718
1420
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1719
|
-
// Create the file logger for matterbridge
|
|
1720
1421
|
if (data.params.value)
|
|
1721
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.
|
|
1422
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1722
1423
|
else
|
|
1723
1424
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
1724
1425
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1745,7 +1446,6 @@ export class Frontend extends EventEmitter {
|
|
|
1745
1446
|
else if (data.params.value === 'Fatal') {
|
|
1746
1447
|
Logger.level = MatterLogLevel.FATAL;
|
|
1747
1448
|
}
|
|
1748
|
-
this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
1749
1449
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
1750
1450
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1751
1451
|
}
|
|
@@ -1753,7 +1453,7 @@ export class Frontend extends EventEmitter {
|
|
|
1753
1453
|
case 'setmjlogfile':
|
|
1754
1454
|
if (isValidBoolean(data.params.value)) {
|
|
1755
1455
|
this.log.debug('Matter file log:', data.params.value);
|
|
1756
|
-
this.matterbridge.
|
|
1456
|
+
this.matterbridge.fileLogger = data.params.value;
|
|
1757
1457
|
await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
|
|
1758
1458
|
if (data.params.value) {
|
|
1759
1459
|
this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
@@ -1768,89 +1468,92 @@ export class Frontend extends EventEmitter {
|
|
|
1768
1468
|
if (isValidString(data.params.value)) {
|
|
1769
1469
|
this.log.debug(`Matter.js mdns interface: ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
|
|
1770
1470
|
this.matterbridge.mdnsInterface = data.params.value === '' ? undefined : data.params.value;
|
|
1771
|
-
this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
|
|
1772
1471
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', data.params.value);
|
|
1773
1472
|
this.wssSendRestartRequired();
|
|
1774
1473
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1474
|
+
this.wssSendSnackbarMessage(`Mdns interface changed to ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
|
|
1775
1475
|
}
|
|
1776
1476
|
break;
|
|
1777
1477
|
case 'setipv4address':
|
|
1778
1478
|
if (isValidString(data.params.value)) {
|
|
1779
1479
|
this.log.debug(`Matter.js ipv4 address: ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
|
|
1780
|
-
this.matterbridge.
|
|
1781
|
-
this.matterbridge.matterbridgeInformation.matterIpv4Address = this.matterbridge.ipv4address;
|
|
1480
|
+
this.matterbridge.ipv4Address = data.params.value === '' ? undefined : data.params.value;
|
|
1782
1481
|
await this.matterbridge.nodeContext?.set('matteripv4address', data.params.value);
|
|
1783
1482
|
this.wssSendRestartRequired();
|
|
1784
1483
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1484
|
+
this.wssSendSnackbarMessage(`IPv4 address changed to ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
|
|
1785
1485
|
}
|
|
1786
1486
|
break;
|
|
1787
1487
|
case 'setipv6address':
|
|
1788
1488
|
if (isValidString(data.params.value)) {
|
|
1789
1489
|
this.log.debug(`Matter.js ipv6 address: ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
|
|
1790
|
-
this.matterbridge.
|
|
1791
|
-
this.matterbridge.matterbridgeInformation.matterIpv6Address = this.matterbridge.ipv6address;
|
|
1490
|
+
this.matterbridge.ipv6Address = data.params.value === '' ? undefined : data.params.value;
|
|
1792
1491
|
await this.matterbridge.nodeContext?.set('matteripv6address', data.params.value);
|
|
1793
1492
|
this.wssSendRestartRequired();
|
|
1794
1493
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1494
|
+
this.wssSendSnackbarMessage(`IPv6 address changed to ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
|
|
1795
1495
|
}
|
|
1796
1496
|
break;
|
|
1797
1497
|
case 'setmatterport':
|
|
1798
|
-
// eslint-disable-next-line no-case-declarations
|
|
1799
1498
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1800
1499
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1801
1500
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
1802
|
-
this.matterbridge.
|
|
1501
|
+
this.matterbridge.port = port;
|
|
1803
1502
|
await this.matterbridge.nodeContext?.set('matterport', port);
|
|
1804
1503
|
this.wssSendRestartRequired();
|
|
1805
1504
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1505
|
+
this.wssSendSnackbarMessage(`Matter port changed to ${port}`);
|
|
1806
1506
|
}
|
|
1807
1507
|
else {
|
|
1808
1508
|
this.log.debug(`Reset matter commissioning port to ${CYAN}5540${db}`);
|
|
1809
|
-
this.matterbridge.
|
|
1509
|
+
this.matterbridge.port = 5540;
|
|
1810
1510
|
await this.matterbridge.nodeContext?.set('matterport', 5540);
|
|
1811
1511
|
this.wssSendRestartRequired();
|
|
1812
1512
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning port to default 5540' });
|
|
1513
|
+
this.wssSendSnackbarMessage(`Matter port reset to default 5540`, undefined, 'error');
|
|
1813
1514
|
}
|
|
1814
1515
|
break;
|
|
1815
1516
|
case 'setmatterdiscriminator':
|
|
1816
|
-
// eslint-disable-next-line no-case-declarations
|
|
1817
1517
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1818
1518
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1819
1519
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
1820
|
-
this.matterbridge.
|
|
1520
|
+
this.matterbridge.discriminator = discriminator;
|
|
1821
1521
|
await this.matterbridge.nodeContext?.set('matterdiscriminator', discriminator);
|
|
1822
1522
|
this.wssSendRestartRequired();
|
|
1823
1523
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1524
|
+
this.wssSendSnackbarMessage(`Matter discriminator changed to ${discriminator}`);
|
|
1824
1525
|
}
|
|
1825
1526
|
else {
|
|
1826
1527
|
this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
|
|
1827
|
-
this.matterbridge.
|
|
1528
|
+
this.matterbridge.discriminator = undefined;
|
|
1828
1529
|
await this.matterbridge.nodeContext?.remove('matterdiscriminator');
|
|
1829
1530
|
this.wssSendRestartRequired();
|
|
1830
1531
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning discriminator to default undefined' });
|
|
1532
|
+
this.wssSendSnackbarMessage(`Matter discriminator reset to default`, undefined, 'error');
|
|
1831
1533
|
}
|
|
1832
1534
|
break;
|
|
1833
1535
|
case 'setmatterpasscode':
|
|
1834
|
-
// eslint-disable-next-line no-case-declarations
|
|
1835
1536
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1836
1537
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1837
|
-
this.matterbridge.
|
|
1538
|
+
this.matterbridge.passcode = passcode;
|
|
1838
1539
|
this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
|
|
1839
1540
|
await this.matterbridge.nodeContext?.set('matterpasscode', passcode);
|
|
1840
1541
|
this.wssSendRestartRequired();
|
|
1841
1542
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1543
|
+
this.wssSendSnackbarMessage(`Matter passcode changed to ${passcode}`);
|
|
1842
1544
|
}
|
|
1843
1545
|
else {
|
|
1844
1546
|
this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
|
|
1845
|
-
this.matterbridge.
|
|
1547
|
+
this.matterbridge.passcode = undefined;
|
|
1846
1548
|
await this.matterbridge.nodeContext?.remove('matterpasscode');
|
|
1847
1549
|
this.wssSendRestartRequired();
|
|
1848
1550
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning passcode to default undefined' });
|
|
1551
|
+
this.wssSendSnackbarMessage(`Matter passcode reset to default`, undefined, 'error');
|
|
1849
1552
|
}
|
|
1850
1553
|
break;
|
|
1851
1554
|
case 'setvirtualmode':
|
|
1852
1555
|
if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
|
|
1853
|
-
this.matterbridge.
|
|
1556
|
+
this.matterbridge.virtualMode = data.params.value;
|
|
1854
1557
|
this.log.debug(`Set matterbridge virtual mode to ${CYAN}${data.params.value}${db}`);
|
|
1855
1558
|
await this.matterbridge.nodeContext?.set('virtualmode', data.params.value);
|
|
1856
1559
|
this.wssSendRestartRequired();
|
|
@@ -1875,19 +1578,15 @@ export class Frontend extends EventEmitter {
|
|
|
1875
1578
|
return;
|
|
1876
1579
|
}
|
|
1877
1580
|
const config = plugin.configJson;
|
|
1878
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1879
1581
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1880
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1881
1582
|
if (select === 'serial')
|
|
1882
1583
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1883
1584
|
if (select === 'name')
|
|
1884
1585
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1885
1586
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1886
|
-
// Remove postfix from the serial if it exists
|
|
1887
1587
|
if (config.postfix) {
|
|
1888
1588
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1889
1589
|
}
|
|
1890
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1891
1590
|
if (isValidArray(config.whiteList, 1)) {
|
|
1892
1591
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1893
1592
|
config.whiteList.push(data.params.serial);
|
|
@@ -1896,7 +1595,6 @@ export class Frontend extends EventEmitter {
|
|
|
1896
1595
|
config.whiteList.push(data.params.name);
|
|
1897
1596
|
}
|
|
1898
1597
|
}
|
|
1899
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1900
1598
|
if (isValidArray(config.blackList, 1)) {
|
|
1901
1599
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1902
1600
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1924,9 +1622,7 @@ export class Frontend extends EventEmitter {
|
|
|
1924
1622
|
return;
|
|
1925
1623
|
}
|
|
1926
1624
|
const config = plugin.configJson;
|
|
1927
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1928
1625
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1929
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1930
1626
|
if (select === 'serial')
|
|
1931
1627
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1932
1628
|
if (select === 'name')
|
|
@@ -1935,7 +1631,6 @@ export class Frontend extends EventEmitter {
|
|
|
1935
1631
|
if (config.postfix) {
|
|
1936
1632
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1937
1633
|
}
|
|
1938
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1939
1634
|
if (isValidArray(config.whiteList, 1)) {
|
|
1940
1635
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1941
1636
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1944,7 +1639,6 @@ export class Frontend extends EventEmitter {
|
|
|
1944
1639
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1945
1640
|
}
|
|
1946
1641
|
}
|
|
1947
|
-
// Add the serial to the blackList
|
|
1948
1642
|
if (isValidArray(config.blackList)) {
|
|
1949
1643
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1950
1644
|
config.blackList.push(data.params.serial);
|
|
@@ -1967,7 +1661,6 @@ export class Frontend extends EventEmitter {
|
|
|
1967
1661
|
}
|
|
1968
1662
|
}
|
|
1969
1663
|
else {
|
|
1970
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1971
1664
|
const localData = data;
|
|
1972
1665
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1973
1666
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1977,44 +1670,21 @@ export class Frontend extends EventEmitter {
|
|
|
1977
1670
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1978
1671
|
}
|
|
1979
1672
|
}
|
|
1980
|
-
/**
|
|
1981
|
-
* Sends a WebSocket log message to all connected clients. The function is called by AnsiLogger.setGlobalCallback.
|
|
1982
|
-
*
|
|
1983
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
1984
|
-
* @param {string} time - The time string of the message
|
|
1985
|
-
* @param {string} name - The logger name of the message
|
|
1986
|
-
* @param {string} message - The content of the message.
|
|
1987
|
-
*
|
|
1988
|
-
* @remarks
|
|
1989
|
-
* The function removes ANSI escape codes, leading asterisks, non-printable characters, and replaces all occurrences of \t and \n.
|
|
1990
|
-
* It also replaces all occurrences of \" with " and angle-brackets with < and >.
|
|
1991
|
-
* The function sends the message to all connected clients.
|
|
1992
|
-
*/
|
|
1993
1673
|
wssSendLogMessage(level, time, name, message) {
|
|
1994
1674
|
if (!level || !time || !name || !message)
|
|
1995
1675
|
return;
|
|
1996
|
-
// Remove ANSI escape codes from the message
|
|
1997
|
-
// eslint-disable-next-line no-control-regex
|
|
1998
1676
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1999
|
-
// Remove leading asterisks from the message
|
|
2000
1677
|
message = message.replace(/^\*+/, '');
|
|
2001
|
-
// Replace all occurrences of \t and \n
|
|
2002
1678
|
message = message.replace(/[\t\n]/g, '');
|
|
2003
|
-
// Remove non-printable characters
|
|
2004
|
-
// eslint-disable-next-line no-control-regex
|
|
2005
1679
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2006
|
-
// Replace all occurrences of \" with "
|
|
2007
1680
|
message = message.replace(/\\"/g, '"');
|
|
2008
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2009
1681
|
const maxContinuousLength = 100;
|
|
2010
1682
|
const keepStartLength = 20;
|
|
2011
1683
|
const keepEndLength = 20;
|
|
2012
|
-
// Split the message into words
|
|
2013
1684
|
if (level !== 'spawn') {
|
|
2014
1685
|
message = message
|
|
2015
1686
|
.split(' ')
|
|
2016
1687
|
.map((word) => {
|
|
2017
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2018
1688
|
if (word.length > maxContinuousLength) {
|
|
2019
1689
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2020
1690
|
}
|
|
@@ -2022,161 +1692,60 @@ export class Frontend extends EventEmitter {
|
|
|
2022
1692
|
})
|
|
2023
1693
|
.join(' ');
|
|
2024
1694
|
}
|
|
2025
|
-
// Send the message to all connected clients
|
|
2026
1695
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2027
1696
|
}
|
|
2028
|
-
/**
|
|
2029
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2030
|
-
*
|
|
2031
|
-
* @param {string} changed - The changed value.
|
|
2032
|
-
* @param {Record<string, unknown>} params - Additional parameters to send with the message.
|
|
2033
|
-
* possible values for changed:
|
|
2034
|
-
* - 'settings' (when the bridge has started in bridge mode or childbridge mode and when update finds a new version)
|
|
2035
|
-
* - 'plugins'
|
|
2036
|
-
* - 'devices'
|
|
2037
|
-
* - 'matter' with param 'matter' (QRDiv component)
|
|
2038
|
-
* @param {ApiMatterResponse} params.matter - The matter device that has changed. Required if changed is 'matter'.
|
|
2039
|
-
*/
|
|
2040
1697
|
wssSendRefreshRequired(changed, params) {
|
|
2041
1698
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2042
|
-
// Send the message to all connected clients
|
|
2043
1699
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2044
1700
|
}
|
|
2045
|
-
/**
|
|
2046
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2047
|
-
*
|
|
2048
|
-
* @param {boolean} snackbar - If true, a snackbar message will be sent to all connected clients. Default is true.
|
|
2049
|
-
* @param {boolean} fixed - If true, the restart is fixed and will not be reset by plugin restarts. Default is false.
|
|
2050
|
-
*/
|
|
2051
1701
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2052
1702
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
2053
|
-
this.matterbridge.
|
|
2054
|
-
this.matterbridge.
|
|
1703
|
+
this.matterbridge.restartRequired = true;
|
|
1704
|
+
this.matterbridge.fixedRestartRequired = fixed;
|
|
2055
1705
|
if (snackbar === true)
|
|
2056
1706
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2057
|
-
// Send the message to all connected clients
|
|
2058
1707
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2059
1708
|
}
|
|
2060
|
-
/**
|
|
2061
|
-
* Sends a no need to restart WebSocket message to all connected clients.
|
|
2062
|
-
*
|
|
2063
|
-
* @param {boolean} snackbar - If true, the snackbar message will be cleared from all connected clients. Default is true.
|
|
2064
|
-
*/
|
|
2065
1709
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2066
1710
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
2067
|
-
this.matterbridge.
|
|
1711
|
+
this.matterbridge.restartRequired = false;
|
|
2068
1712
|
if (snackbar === true)
|
|
2069
1713
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2070
|
-
// Send the message to all connected clients
|
|
2071
1714
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2072
1715
|
}
|
|
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. Default is false.
|
|
2077
|
-
*/
|
|
2078
1716
|
wssSendUpdateRequired(devVersion = false) {
|
|
2079
1717
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2080
|
-
this.matterbridge.
|
|
2081
|
-
// Send the message to all connected clients
|
|
1718
|
+
this.matterbridge.updateRequired = true;
|
|
2082
1719
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2083
1720
|
}
|
|
2084
|
-
/**
|
|
2085
|
-
* Sends a cpu update message to all connected clients.
|
|
2086
|
-
*
|
|
2087
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2088
|
-
*/
|
|
2089
1721
|
wssSendCpuUpdate(cpuUsage) {
|
|
2090
1722
|
if (hasParameter('debug'))
|
|
2091
1723
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2092
|
-
// Send the message to all connected clients
|
|
2093
1724
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
|
|
2094
1725
|
}
|
|
2095
|
-
/**
|
|
2096
|
-
* Sends a memory update message to all connected clients.
|
|
2097
|
-
*
|
|
2098
|
-
* @param {string} totalMemory - The total memory in bytes.
|
|
2099
|
-
* @param {string} freeMemory - The free memory in bytes.
|
|
2100
|
-
* @param {string} rss - The resident set size in bytes.
|
|
2101
|
-
* @param {string} heapTotal - The total heap memory in bytes.
|
|
2102
|
-
* @param {string} heapUsed - The used heap memory in bytes.
|
|
2103
|
-
* @param {string} external - The external memory in bytes.
|
|
2104
|
-
* @param {string} arrayBuffers - The array buffers memory in bytes.
|
|
2105
|
-
*/
|
|
2106
1726
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2107
1727
|
if (hasParameter('debug'))
|
|
2108
1728
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2109
|
-
// Send the message to all connected clients
|
|
2110
1729
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2111
1730
|
}
|
|
2112
|
-
/**
|
|
2113
|
-
* Sends an uptime update message to all connected clients.
|
|
2114
|
-
*
|
|
2115
|
-
* @param {string} systemUptime - The system uptime in a human-readable format.
|
|
2116
|
-
* @param {string} processUptime - The process uptime in a human-readable format.
|
|
2117
|
-
*/
|
|
2118
1731
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2119
1732
|
if (hasParameter('debug'))
|
|
2120
1733
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2121
|
-
// Send the message to all connected clients
|
|
2122
1734
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2123
1735
|
}
|
|
2124
|
-
/**
|
|
2125
|
-
* Sends an open snackbar message to all connected clients.
|
|
2126
|
-
*
|
|
2127
|
-
* @param {string} message - The message to send.
|
|
2128
|
-
* @param {number} timeout - The timeout in seconds for the snackbar message. Default is 5 seconds.
|
|
2129
|
-
* @param {'info' | 'warning' | 'error' | 'success'} severity - The severity of the message.
|
|
2130
|
-
* possible values are: 'info', 'warning', 'error', 'success'. Default is 'info'.
|
|
2131
|
-
*
|
|
2132
|
-
* @remarks
|
|
2133
|
-
* If timeout is 0, the snackbar message will be displayed until closed by the user.
|
|
2134
|
-
*/
|
|
2135
1736
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2136
1737
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2137
|
-
// Send the message to all connected clients
|
|
2138
1738
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2139
1739
|
}
|
|
2140
|
-
/**
|
|
2141
|
-
* Sends a close snackbar message to all connected clients.
|
|
2142
|
-
* It will close the snackbar message with the same message and timeout = 0.
|
|
2143
|
-
*
|
|
2144
|
-
* @param {string} message - The message to send.
|
|
2145
|
-
*/
|
|
2146
1740
|
wssSendCloseSnackbarMessage(message) {
|
|
2147
1741
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2148
|
-
// Send the message to all connected clients
|
|
2149
1742
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2150
1743
|
}
|
|
2151
|
-
/**
|
|
2152
|
-
* Sends an attribute update message to all connected WebSocket clients.
|
|
2153
|
-
*
|
|
2154
|
-
* @param {string | undefined} plugin - The name of the plugin.
|
|
2155
|
-
* @param {string | undefined} serialNumber - The serial number of the device.
|
|
2156
|
-
* @param {string | undefined} uniqueId - The unique identifier of the device.
|
|
2157
|
-
* @param {EndpointNumber} number - The endpoint number where the attribute belongs.
|
|
2158
|
-
* @param {string} id - The endpoint id where the attribute belongs.
|
|
2159
|
-
* @param {string} cluster - The cluster name where the attribute belongs.
|
|
2160
|
-
* @param {string} attribute - The name of the attribute that changed.
|
|
2161
|
-
* @param {number | string | boolean} value - The new value of the attribute.
|
|
2162
|
-
*
|
|
2163
|
-
* @remarks
|
|
2164
|
-
* This method logs a debug message and sends a JSON-formatted message to all connected WebSocket clients
|
|
2165
|
-
* with the updated attribute information.
|
|
2166
|
-
*/
|
|
2167
1744
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2168
1745
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2169
|
-
// Send the message to all connected clients
|
|
2170
1746
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2171
1747
|
}
|
|
2172
|
-
/**
|
|
2173
|
-
* Sends a message to all connected clients.
|
|
2174
|
-
* This is an helper function to send a broadcast message to all connected clients.
|
|
2175
|
-
*
|
|
2176
|
-
* @param {WsMessageBroadcast} msg - The message to send.
|
|
2177
|
-
*/
|
|
2178
1748
|
wssBroadcastMessage(msg) {
|
|
2179
|
-
// Send the message to all connected clients
|
|
2180
1749
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2181
1750
|
if (msg.method !== 'log')
|
|
2182
1751
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -2187,4 +1756,3 @@ export class Frontend extends EventEmitter {
|
|
|
2187
1756
|
});
|
|
2188
1757
|
}
|
|
2189
1758
|
}
|
|
2190
|
-
//# sourceMappingURL=frontend.js.map
|