matterbridge 3.3.0 → 3.3.1-dev-20251008-e61b8db
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 +34 -3
- package/README.md +9 -1
- package/dist/broadcastServer.js +73 -0
- package/dist/broadcastServerTypes.js +1 -0
- package/dist/cli.js +38 -99
- 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 +233 -634
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +4 -57
- package/dist/index.js +2 -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 +156 -885
- 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 +4 -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,50 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
import { createServer } from 'node:http';
|
|
26
|
-
import https from 'node:https';
|
|
1
|
+
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
2
|
+
console.log('\u001B[32mFrontend loaded.\u001B[40;0m');
|
|
27
3
|
import os from 'node:os';
|
|
28
4
|
import path from 'node:path';
|
|
29
|
-
import { existsSync, promises as fs, unlinkSync } from 'node:fs';
|
|
30
5
|
import EventEmitter from 'node:events';
|
|
31
|
-
import { appendFile } from 'node:fs/promises';
|
|
32
|
-
// Third-party modules
|
|
33
6
|
import express from 'express';
|
|
34
7
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
35
8
|
import multer from 'multer';
|
|
36
|
-
|
|
37
|
-
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt } from 'node-ansi-logger';
|
|
38
|
-
// @matter
|
|
9
|
+
import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, YELLOW, nt, wr } from 'node-ansi-logger';
|
|
39
10
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle, LogDestination, Diagnostic, Time, FabricIndex } from '@matter/main';
|
|
40
|
-
import { BridgedDeviceBasicInformation
|
|
11
|
+
import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
|
|
12
|
+
import { PowerSource } from '@matter/main/clusters/power-source';
|
|
41
13
|
import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/main/protocol';
|
|
42
14
|
import { CommissioningOptions } from '@matter/main/types';
|
|
43
|
-
|
|
15
|
+
import { MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from './matterbridgeTypes.js';
|
|
44
16
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout, hasParameter, wait, inspectError } from './utils/export.js';
|
|
45
|
-
import {
|
|
17
|
+
import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
|
|
46
18
|
import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
47
19
|
import { cliEmitter, lastCpuUsage } from './cliEmitter.js';
|
|
20
|
+
import { BroadcastServer } from './broadcastServer.js';
|
|
48
21
|
export class Frontend extends EventEmitter {
|
|
49
22
|
matterbridge;
|
|
50
23
|
log;
|
|
@@ -54,11 +27,65 @@ export class Frontend extends EventEmitter {
|
|
|
54
27
|
httpServer;
|
|
55
28
|
httpsServer;
|
|
56
29
|
webSocketServer;
|
|
30
|
+
server;
|
|
57
31
|
constructor(matterbridge) {
|
|
58
32
|
super();
|
|
59
33
|
this.matterbridge = matterbridge;
|
|
60
|
-
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4
|
|
34
|
+
this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
61
35
|
this.log.logNameColor = '\x1b[38;5;97m';
|
|
36
|
+
this.server = new BroadcastServer('plugins', this.log);
|
|
37
|
+
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
38
|
+
}
|
|
39
|
+
destroy() {
|
|
40
|
+
this.server.close();
|
|
41
|
+
}
|
|
42
|
+
async msgHandler(msg) {
|
|
43
|
+
if (this.server.isWorkerRequest(msg, msg.type)) {
|
|
44
|
+
if (!msg.id || (msg.dst !== 'all' && msg.dst !== 'frontend'))
|
|
45
|
+
return;
|
|
46
|
+
this.log.debug(`**Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
47
|
+
switch (msg.type) {
|
|
48
|
+
case 'frontend_start':
|
|
49
|
+
await this.start(msg.params.port);
|
|
50
|
+
this.server.respond({ ...msg, id: msg.id, response: { success: true } });
|
|
51
|
+
break;
|
|
52
|
+
case 'frontend_stop':
|
|
53
|
+
await this.stop();
|
|
54
|
+
this.server.respond({ ...msg, id: msg.id, response: { success: true } });
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
this.log.warn(`Unknown broadcast request ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (this.server.isWorkerResponse(msg, msg.type)) {
|
|
61
|
+
this.log.debug(`**Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
62
|
+
switch (msg.type) {
|
|
63
|
+
case 'plugins_install':
|
|
64
|
+
this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
|
|
65
|
+
if (msg.response.success) {
|
|
66
|
+
this.wssSendRestartRequired(true, true);
|
|
67
|
+
this.wssSendRefreshRequired('plugins');
|
|
68
|
+
this.wssSendSnackbarMessage(`Installed package ${msg.response.packageName}`, 5, 'success');
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not installed`, 10, 'error');
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case 'plugins_uninstall':
|
|
75
|
+
this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.response.packageName}...`);
|
|
76
|
+
if (msg.response.success) {
|
|
77
|
+
this.wssSendRestartRequired(true, true);
|
|
78
|
+
this.wssSendRefreshRequired('plugins');
|
|
79
|
+
this.wssSendSnackbarMessage(`Uninstalled package ${msg.response.packageName}`, 5, 'success');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not uninstalled`, 10, 'error');
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
this.log.warn(`Unknown broadcast response ${CYAN}${msg.type}${wr} from ${CYAN}${msg.src}${wr}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
62
89
|
}
|
|
63
90
|
set logLevel(logLevel) {
|
|
64
91
|
this.log.logLevel = logLevel;
|
|
@@ -66,61 +93,21 @@ export class Frontend extends EventEmitter {
|
|
|
66
93
|
async start(port = 8283) {
|
|
67
94
|
this.port = port;
|
|
68
95
|
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
|
|
96
|
+
const uploadDir = path.join(this.matterbridge.matterbridgeDirectory, 'uploads');
|
|
71
97
|
const upload = multer({ dest: uploadDir });
|
|
72
|
-
// Create the express app that serves the frontend
|
|
73
98
|
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
99
|
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
100
|
if (!hasParameter('ssl')) {
|
|
113
|
-
|
|
101
|
+
const http = await import('node:http');
|
|
114
102
|
try {
|
|
115
103
|
this.log.debug(`Creating HTTP server...`);
|
|
116
|
-
this.httpServer = createServer(this.expressApp);
|
|
104
|
+
this.httpServer = http.createServer(this.expressApp);
|
|
117
105
|
}
|
|
118
106
|
catch (error) {
|
|
119
107
|
this.log.error(`Failed to create HTTP server: ${error}`);
|
|
120
108
|
this.emit('server_error', error);
|
|
121
109
|
return;
|
|
122
110
|
}
|
|
123
|
-
// Listen on the specified port
|
|
124
111
|
if (hasParameter('ingress')) {
|
|
125
112
|
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
126
113
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -160,10 +147,10 @@ export class Frontend extends EventEmitter {
|
|
|
160
147
|
let pfx;
|
|
161
148
|
let passphrase;
|
|
162
149
|
let httpsServerOptions = {};
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
const fs = await import('node:fs');
|
|
151
|
+
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'))) {
|
|
165
152
|
try {
|
|
166
|
-
pfx = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
153
|
+
pfx = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12'));
|
|
167
154
|
this.log.info(`Loaded p12 certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.p12')}`);
|
|
168
155
|
}
|
|
169
156
|
catch (error) {
|
|
@@ -172,8 +159,8 @@ export class Frontend extends EventEmitter {
|
|
|
172
159
|
return;
|
|
173
160
|
}
|
|
174
161
|
try {
|
|
175
|
-
passphrase = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
176
|
-
passphrase = passphrase.trim();
|
|
162
|
+
passphrase = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass'), 'utf8');
|
|
163
|
+
passphrase = passphrase.trim();
|
|
177
164
|
this.log.info(`Loaded p12 passphrase file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pass')}`);
|
|
178
165
|
}
|
|
179
166
|
catch (error) {
|
|
@@ -184,9 +171,8 @@ export class Frontend extends EventEmitter {
|
|
|
184
171
|
httpsServerOptions = { pfx, passphrase };
|
|
185
172
|
}
|
|
186
173
|
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
174
|
try {
|
|
189
|
-
cert = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
175
|
+
cert = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
190
176
|
this.log.info(`Loaded certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
191
177
|
}
|
|
192
178
|
catch (error) {
|
|
@@ -195,7 +181,7 @@ export class Frontend extends EventEmitter {
|
|
|
195
181
|
return;
|
|
196
182
|
}
|
|
197
183
|
try {
|
|
198
|
-
key = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
184
|
+
key = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
199
185
|
this.log.info(`Loaded key file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/key.pem')}`);
|
|
200
186
|
}
|
|
201
187
|
catch (error) {
|
|
@@ -204,7 +190,7 @@ export class Frontend extends EventEmitter {
|
|
|
204
190
|
return;
|
|
205
191
|
}
|
|
206
192
|
try {
|
|
207
|
-
ca = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
|
|
193
|
+
ca = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
|
|
208
194
|
fullChain = `${cert}\n${ca}`;
|
|
209
195
|
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridge.matterbridgeDirectory, 'certs/ca.pem')}`);
|
|
210
196
|
}
|
|
@@ -214,10 +200,10 @@ export class Frontend extends EventEmitter {
|
|
|
214
200
|
httpsServerOptions = { cert: fullChain ?? cert, key, ca };
|
|
215
201
|
}
|
|
216
202
|
if (hasParameter('mtls')) {
|
|
217
|
-
httpsServerOptions.requestCert = true;
|
|
218
|
-
httpsServerOptions.rejectUnauthorized = true;
|
|
203
|
+
httpsServerOptions.requestCert = true;
|
|
204
|
+
httpsServerOptions.rejectUnauthorized = true;
|
|
219
205
|
}
|
|
220
|
-
|
|
206
|
+
const https = await import('node:https');
|
|
221
207
|
try {
|
|
222
208
|
this.log.debug(`Creating HTTPS server...`);
|
|
223
209
|
this.httpsServer = https.createServer(httpsServerOptions, this.expressApp);
|
|
@@ -227,7 +213,6 @@ export class Frontend extends EventEmitter {
|
|
|
227
213
|
this.emit('server_error', error);
|
|
228
214
|
return;
|
|
229
215
|
}
|
|
230
|
-
// Listen on the specified port
|
|
231
216
|
if (hasParameter('ingress')) {
|
|
232
217
|
this.httpsServer.listen(this.port, '0.0.0.0', () => {
|
|
233
218
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${this.port}${UNDERLINEOFF}${rs}`);
|
|
@@ -259,19 +244,15 @@ export class Frontend extends EventEmitter {
|
|
|
259
244
|
return;
|
|
260
245
|
});
|
|
261
246
|
}
|
|
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}...`);
|
|
247
|
+
this.log.debug(`Creating WebSocketServer...`);
|
|
266
248
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
267
249
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
268
250
|
const clientIp = request.socket.remoteAddress;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
251
|
+
let callbackLogLevel = "notice";
|
|
252
|
+
if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
|
|
253
|
+
callbackLogLevel = "info";
|
|
254
|
+
if (this.matterbridge.getLogLevel() === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
255
|
+
callbackLogLevel = "debug";
|
|
275
256
|
AnsiLogger.setGlobalCallback(this.wssSendLogMessage.bind(this), callbackLogLevel);
|
|
276
257
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
277
258
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
@@ -293,7 +274,6 @@ export class Frontend extends EventEmitter {
|
|
|
293
274
|
}
|
|
294
275
|
});
|
|
295
276
|
ws.on('error', (error) => {
|
|
296
|
-
// istanbul ignore next
|
|
297
277
|
this.log.error(`WebSocket client error: ${error}`);
|
|
298
278
|
});
|
|
299
279
|
});
|
|
@@ -301,13 +281,12 @@ export class Frontend extends EventEmitter {
|
|
|
301
281
|
this.log.debug(`WebSocketServer closed`);
|
|
302
282
|
});
|
|
303
283
|
this.webSocketServer.on('listening', () => {
|
|
304
|
-
this.log.info(`The WebSocketServer is listening
|
|
305
|
-
this.emit('websocket_server_listening',
|
|
284
|
+
this.log.info(`The WebSocketServer is listening`);
|
|
285
|
+
this.emit('websocket_server_listening', hasParameter('ssl') ? 'wss' : 'ws');
|
|
306
286
|
});
|
|
307
287
|
this.webSocketServer.on('error', (ws, error) => {
|
|
308
288
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
309
289
|
});
|
|
310
|
-
// Subscribe to cli events
|
|
311
290
|
cliEmitter.removeAllListeners();
|
|
312
291
|
cliEmitter.on('uptime', (systemUptime, processUptime) => {
|
|
313
292
|
this.wssSendUptimeUpdate(systemUptime, processUptime);
|
|
@@ -318,8 +297,6 @@ export class Frontend extends EventEmitter {
|
|
|
318
297
|
cliEmitter.on('cpu', (cpuUsage) => {
|
|
319
298
|
this.wssSendCpuUpdate(cpuUsage);
|
|
320
299
|
});
|
|
321
|
-
// Endpoint to validate login code
|
|
322
|
-
// curl -X POST "http://localhost:8283/api/login" -H "Content-Type: application/json" -d "{\"password\":\"Here\"}"
|
|
323
300
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
324
301
|
const { password } = req.body;
|
|
325
302
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -338,48 +315,41 @@ export class Frontend extends EventEmitter {
|
|
|
338
315
|
this.log.warn('/api/login error wrong password');
|
|
339
316
|
res.json({ valid: false });
|
|
340
317
|
}
|
|
341
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
342
318
|
}
|
|
343
319
|
catch (error) {
|
|
344
320
|
this.log.error('/api/login error getting password');
|
|
345
321
|
res.json({ valid: false });
|
|
346
322
|
}
|
|
347
323
|
});
|
|
348
|
-
// Endpoint to provide health check for docker
|
|
349
324
|
this.expressApp.get('/health', (req, res) => {
|
|
350
325
|
this.log.debug('Express received /health');
|
|
351
326
|
const healthStatus = {
|
|
352
|
-
status: 'ok',
|
|
353
|
-
uptime: process.uptime(),
|
|
354
|
-
timestamp: new Date().toISOString(),
|
|
327
|
+
status: 'ok',
|
|
328
|
+
uptime: process.uptime(),
|
|
329
|
+
timestamp: new Date().toISOString(),
|
|
355
330
|
};
|
|
356
331
|
res.status(200).json(healthStatus);
|
|
357
332
|
});
|
|
358
|
-
// Endpoint to provide memory usage details
|
|
359
333
|
this.expressApp.get('/memory', async (req, res) => {
|
|
360
334
|
this.log.debug('Express received /memory');
|
|
361
|
-
// Memory usage from process
|
|
362
335
|
const memoryUsageRaw = process.memoryUsage();
|
|
363
336
|
const memoryUsage = {
|
|
364
|
-
rss:
|
|
365
|
-
heapTotal:
|
|
366
|
-
heapUsed:
|
|
367
|
-
external:
|
|
368
|
-
arrayBuffers:
|
|
337
|
+
rss: formatMemoryUsage(memoryUsageRaw.rss),
|
|
338
|
+
heapTotal: formatMemoryUsage(memoryUsageRaw.heapTotal),
|
|
339
|
+
heapUsed: formatMemoryUsage(memoryUsageRaw.heapUsed),
|
|
340
|
+
external: formatMemoryUsage(memoryUsageRaw.external),
|
|
341
|
+
arrayBuffers: formatMemoryUsage(memoryUsageRaw.arrayBuffers),
|
|
369
342
|
};
|
|
370
|
-
// V8 heap statistics
|
|
371
343
|
const { default: v8 } = await import('node:v8');
|
|
372
344
|
const heapStatsRaw = v8.getHeapStatistics();
|
|
373
345
|
const heapSpacesRaw = v8.getHeapSpaceStatistics();
|
|
374
|
-
|
|
375
|
-
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, this.formatMemoryUsage(value)]));
|
|
376
|
-
// Format heapSpaces
|
|
346
|
+
const heapStats = Object.fromEntries(Object.entries(heapStatsRaw).map(([key, value]) => [key, formatMemoryUsage(value)]));
|
|
377
347
|
const heapSpaces = heapSpacesRaw.map((space) => ({
|
|
378
348
|
...space,
|
|
379
|
-
space_size:
|
|
380
|
-
space_used_size:
|
|
381
|
-
space_available_size:
|
|
382
|
-
physical_space_size:
|
|
349
|
+
space_size: formatMemoryUsage(space.space_size),
|
|
350
|
+
space_used_size: formatMemoryUsage(space.space_used_size),
|
|
351
|
+
space_available_size: formatMemoryUsage(space.space_available_size),
|
|
352
|
+
physical_space_size: formatMemoryUsage(space.physical_space_size),
|
|
383
353
|
}));
|
|
384
354
|
const { createRequire } = await import('node:module');
|
|
385
355
|
const require = createRequire(import.meta.url);
|
|
@@ -392,27 +362,23 @@ export class Frontend extends EventEmitter {
|
|
|
392
362
|
};
|
|
393
363
|
res.status(200).json(memoryReport);
|
|
394
364
|
});
|
|
395
|
-
// Endpoint to provide settings
|
|
396
365
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
397
366
|
this.log.debug('The frontend sent /api/settings');
|
|
398
367
|
res.json(await this.getApiSettings());
|
|
399
368
|
});
|
|
400
|
-
// Endpoint to provide plugins
|
|
401
369
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
402
370
|
this.log.debug('The frontend sent /api/plugins');
|
|
403
|
-
res.json(this.getPlugins());
|
|
371
|
+
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getPlugins());
|
|
404
372
|
});
|
|
405
|
-
// Endpoint to provide devices
|
|
406
373
|
this.expressApp.get('/api/devices', async (req, res) => {
|
|
407
374
|
this.log.debug('The frontend sent /api/devices');
|
|
408
|
-
|
|
409
|
-
res.json(devices);
|
|
375
|
+
res.json(this.matterbridge.hasCleanupStarted ? [] : this.getDevices());
|
|
410
376
|
});
|
|
411
|
-
// Endpoint to view the matterbridge log
|
|
412
377
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
413
378
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
414
379
|
try {
|
|
415
|
-
const
|
|
380
|
+
const fs = await import('node:fs');
|
|
381
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
|
|
416
382
|
res.type('text/plain');
|
|
417
383
|
res.send(data);
|
|
418
384
|
}
|
|
@@ -421,11 +387,11 @@ export class Frontend extends EventEmitter {
|
|
|
421
387
|
res.status(500).send('Error reading matterbridge log file. Please enable the matterbridge log on file in the settings.');
|
|
422
388
|
}
|
|
423
389
|
});
|
|
424
|
-
// Endpoint to view the matter.js log
|
|
425
390
|
this.expressApp.get('/api/view-mjlog', async (req, res) => {
|
|
426
391
|
this.log.debug('The frontend sent /api/view-mjlog');
|
|
427
392
|
try {
|
|
428
|
-
const
|
|
393
|
+
const fs = await import('node:fs');
|
|
394
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
|
|
429
395
|
res.type('text/plain');
|
|
430
396
|
res.send(data);
|
|
431
397
|
}
|
|
@@ -434,11 +400,9 @@ export class Frontend extends EventEmitter {
|
|
|
434
400
|
res.status(500).send('Error reading matter log file. Please enable the matter log on file in the settings.');
|
|
435
401
|
}
|
|
436
402
|
});
|
|
437
|
-
// Endpoint to view the diagnostic.log
|
|
438
403
|
this.expressApp.get('/api/view-diagnostic', async (req, res) => {
|
|
439
404
|
this.log.debug('The frontend sent /api/view-diagnostic');
|
|
440
405
|
const serverNodes = [];
|
|
441
|
-
// istanbul ignore else
|
|
442
406
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
443
407
|
if (this.matterbridge.serverNode)
|
|
444
408
|
serverNodes.push(this.matterbridge.serverNode);
|
|
@@ -449,16 +413,16 @@ export class Frontend extends EventEmitter {
|
|
|
449
413
|
serverNodes.push(plugin.serverNode);
|
|
450
414
|
}
|
|
451
415
|
}
|
|
452
|
-
// istanbul ignore next
|
|
453
416
|
for (const device of this.matterbridge.devices.array()) {
|
|
454
417
|
if (device.serverNode)
|
|
455
418
|
serverNodes.push(device.serverNode);
|
|
456
419
|
}
|
|
457
|
-
|
|
458
|
-
|
|
420
|
+
const fs = await import('node:fs');
|
|
421
|
+
if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log')))
|
|
422
|
+
fs.unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'));
|
|
459
423
|
const diagnosticDestination = LogDestination({ name: 'diagnostic', level: MatterLogLevel.INFO, format: MatterLogFormat.formats.plain });
|
|
460
424
|
diagnosticDestination.write = async (text, _message) => {
|
|
461
|
-
await appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), text + '\n', { encoding: 'utf8' });
|
|
425
|
+
await fs.promises.appendFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), text + '\n', { encoding: 'utf8' });
|
|
462
426
|
};
|
|
463
427
|
Logger.destinations.diagnostic = diagnosticDestination;
|
|
464
428
|
if (!diagnosticDestination.context) {
|
|
@@ -472,24 +436,23 @@ export class Frontend extends EventEmitter {
|
|
|
472
436
|
values: [...serverNodes],
|
|
473
437
|
})));
|
|
474
438
|
delete Logger.destinations.diagnostic;
|
|
475
|
-
await wait(500);
|
|
439
|
+
await wait(500);
|
|
476
440
|
try {
|
|
477
|
-
const
|
|
441
|
+
const fs = await import('node:fs');
|
|
442
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'diagnostic.log'), 'utf8');
|
|
478
443
|
res.type('text/plain');
|
|
479
444
|
res.send(data.slice(29));
|
|
480
445
|
}
|
|
481
446
|
catch (error) {
|
|
482
|
-
// istanbul ignore next
|
|
483
447
|
this.log.error(`Error reading diagnostic log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
484
|
-
// istanbul ignore next
|
|
485
448
|
res.status(500).send('Error reading diagnostic log file.');
|
|
486
449
|
}
|
|
487
450
|
});
|
|
488
|
-
// Endpoint to view the shelly log
|
|
489
451
|
this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
|
|
490
452
|
this.log.debug('The frontend sent /api/shellyviewsystemlog');
|
|
491
453
|
try {
|
|
492
|
-
const
|
|
454
|
+
const fs = await import('node:fs');
|
|
455
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
493
456
|
res.type('text/plain');
|
|
494
457
|
res.send(data);
|
|
495
458
|
}
|
|
@@ -498,149 +461,132 @@ export class Frontend extends EventEmitter {
|
|
|
498
461
|
res.status(500).send('Error reading shelly log file. Please create the shelly system log before loading it.');
|
|
499
462
|
}
|
|
500
463
|
});
|
|
501
|
-
// Endpoint to download the matterbridge log
|
|
502
464
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
503
465
|
this.log.debug(`The frontend sent /api/download-mblog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
466
|
+
const fs = await import('node:fs');
|
|
504
467
|
try {
|
|
505
|
-
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
|
|
506
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
|
|
507
|
-
await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
|
|
468
|
+
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), fs.constants.F_OK);
|
|
469
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), 'utf8');
|
|
470
|
+
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), data, 'utf-8');
|
|
508
471
|
}
|
|
509
472
|
catch (error) {
|
|
510
|
-
await fs.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
|
|
473
|
+
await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'Enable the matterbridge log on file in the settings to download the matterbridge log.', 'utf-8');
|
|
511
474
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
512
475
|
}
|
|
513
476
|
res.type('text/plain');
|
|
514
477
|
res.download(path.join(os.tmpdir(), MATTERBRIDGE_LOGGER_FILE), 'matterbridge.log', (error) => {
|
|
515
|
-
/* istanbul ignore if */
|
|
516
478
|
if (error) {
|
|
517
479
|
this.log.error(`Error downloading log file ${MATTERBRIDGE_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
518
480
|
res.status(500).send('Error downloading the matterbridge log file');
|
|
519
481
|
}
|
|
520
482
|
});
|
|
521
483
|
});
|
|
522
|
-
// Endpoint to download the matter log
|
|
523
484
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
524
485
|
this.log.debug(`The frontend sent /api/download-mjlog ${path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE)}`);
|
|
486
|
+
const fs = await import('node:fs');
|
|
525
487
|
try {
|
|
526
|
-
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
|
|
527
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
|
|
528
|
-
await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
|
|
488
|
+
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), fs.constants.F_OK);
|
|
489
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE), 'utf8');
|
|
490
|
+
await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), data, 'utf-8');
|
|
529
491
|
}
|
|
530
492
|
catch (error) {
|
|
531
|
-
await fs.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
|
|
493
|
+
await fs.promises.writeFile(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'Enable the matter log on file in the settings to download the matter log.', 'utf-8');
|
|
532
494
|
this.log.debug(`Error in /api/download-mblog: ${error instanceof Error ? error.message : error}`);
|
|
533
495
|
}
|
|
534
496
|
res.type('text/plain');
|
|
535
497
|
res.download(path.join(os.tmpdir(), MATTER_LOGGER_FILE), 'matter.log', (error) => {
|
|
536
|
-
/* istanbul ignore if */
|
|
537
498
|
if (error) {
|
|
538
499
|
this.log.error(`Error downloading log file ${MATTER_LOGGER_FILE}: ${error instanceof Error ? error.message : error}`);
|
|
539
500
|
res.status(500).send('Error downloading the matter log file');
|
|
540
501
|
}
|
|
541
502
|
});
|
|
542
503
|
});
|
|
543
|
-
// Endpoint to download the shelly log
|
|
544
504
|
this.expressApp.get('/api/shellydownloadsystemlog', async (req, res) => {
|
|
545
505
|
this.log.debug('The frontend sent /api/shellydownloadsystemlog');
|
|
506
|
+
const fs = await import('node:fs');
|
|
546
507
|
try {
|
|
547
|
-
await fs.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
548
|
-
const data = await fs.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
549
|
-
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
508
|
+
await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), fs.constants.F_OK);
|
|
509
|
+
const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, 'shelly.log'), 'utf8');
|
|
510
|
+
await fs.promises.writeFile(path.join(os.tmpdir(), 'shelly.log'), data, 'utf-8');
|
|
550
511
|
}
|
|
551
512
|
catch (error) {
|
|
552
|
-
await fs.writeFile(path.join(os.tmpdir(), 'shelly.log'), 'Create the Shelly system log before downloading it.', 'utf-8');
|
|
513
|
+
await fs.promises.writeFile(path.join(os.tmpdir(), 'shelly.log'), 'Create the Shelly system log before downloading it.', 'utf-8');
|
|
553
514
|
this.log.debug(`Error in /api/shellydownloadsystemlog: ${error instanceof Error ? error.message : error}`);
|
|
554
515
|
}
|
|
555
516
|
res.type('text/plain');
|
|
556
517
|
res.download(path.join(os.tmpdir(), 'shelly.log'), 'shelly.log', (error) => {
|
|
557
|
-
/* istanbul ignore if */
|
|
558
518
|
if (error) {
|
|
559
519
|
this.log.error(`Error downloading Shelly system log file: ${error instanceof Error ? error.message : error}`);
|
|
560
520
|
res.status(500).send('Error downloading Shelly system log file');
|
|
561
521
|
}
|
|
562
522
|
});
|
|
563
523
|
});
|
|
564
|
-
// Endpoint to download the matterbridge storage directory
|
|
565
524
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
566
525
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
567
526
|
await createZip(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
568
527
|
res.download(path.join(os.tmpdir(), `matterbridge.${NODE_STORAGE_DIR}.zip`), `matterbridge.${NODE_STORAGE_DIR}.zip`, (error) => {
|
|
569
|
-
/* istanbul ignore if */
|
|
570
528
|
if (error) {
|
|
571
529
|
this.log.error(`Error downloading file ${`matterbridge.${NODE_STORAGE_DIR}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
572
530
|
res.status(500).send('Error downloading the matterbridge storage file');
|
|
573
531
|
}
|
|
574
532
|
});
|
|
575
533
|
});
|
|
576
|
-
// Endpoint to download the matter storage file
|
|
577
534
|
this.expressApp.get('/api/download-mjstorage', async (req, res) => {
|
|
578
535
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
579
536
|
await createZip(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
580
537
|
res.download(path.join(os.tmpdir(), `matterbridge.${MATTER_STORAGE_NAME}.zip`), `matterbridge.${MATTER_STORAGE_NAME}.zip`, (error) => {
|
|
581
|
-
/* istanbul ignore if */
|
|
582
538
|
if (error) {
|
|
583
539
|
this.log.error(`Error downloading the matter storage matterbridge.${MATTER_STORAGE_NAME}.zip: ${error instanceof Error ? error.message : error}`);
|
|
584
540
|
res.status(500).send('Error downloading the matter storage zip file');
|
|
585
541
|
}
|
|
586
542
|
});
|
|
587
543
|
});
|
|
588
|
-
// Endpoint to download the matterbridge plugin directory
|
|
589
544
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
590
545
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
591
546
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridge.matterbridgePluginDirectory);
|
|
592
547
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
593
|
-
/* istanbul ignore if */
|
|
594
548
|
if (error) {
|
|
595
549
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
596
550
|
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
597
551
|
}
|
|
598
552
|
});
|
|
599
553
|
});
|
|
600
|
-
// Endpoint to download the matterbridge plugin config files
|
|
601
554
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
602
555
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
603
556
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridge.matterbridgeDirectory, '*.config.json')));
|
|
604
557
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
605
|
-
/* istanbul ignore if */
|
|
606
558
|
if (error) {
|
|
607
559
|
this.log.error(`Error downloading file matterbridge.pluginconfig.zip: ${error instanceof Error ? error.message : error}`);
|
|
608
560
|
res.status(500).send('Error downloading the matterbridge plugin config file');
|
|
609
561
|
}
|
|
610
562
|
});
|
|
611
563
|
});
|
|
612
|
-
// Endpoint to download the matterbridge backup (created with the backup command)
|
|
613
564
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
614
565
|
this.log.debug('The frontend sent /api/download-backup');
|
|
615
566
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
616
|
-
/* istanbul ignore if */
|
|
617
567
|
if (error) {
|
|
618
568
|
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
619
569
|
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
620
570
|
}
|
|
621
571
|
});
|
|
622
572
|
});
|
|
623
|
-
// Endpoint to upload a package
|
|
624
573
|
this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
|
|
625
574
|
const { filename } = req.body;
|
|
626
575
|
const file = req.file;
|
|
627
|
-
/* istanbul ignore if */
|
|
628
576
|
if (!file || !filename) {
|
|
629
577
|
this.log.error(`uploadpackage: invalid request: file and filename are required`);
|
|
630
578
|
res.status(400).send('Invalid request: file and filename are required');
|
|
631
579
|
return;
|
|
632
580
|
}
|
|
633
581
|
this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
|
|
634
|
-
// Define the path where the plugin file will be saved
|
|
635
582
|
const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
|
|
636
583
|
try {
|
|
637
|
-
|
|
638
|
-
await fs.rename(file.path, filePath);
|
|
584
|
+
const fs = await import('node:fs');
|
|
585
|
+
await fs.promises.rename(file.path, filePath);
|
|
639
586
|
this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
|
|
640
|
-
// Install the plugin package
|
|
641
587
|
if (filename.endsWith('.tgz')) {
|
|
642
588
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
643
|
-
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], filename);
|
|
589
|
+
await spawnCommand(this.matterbridge, 'npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename);
|
|
644
590
|
this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
|
|
645
591
|
this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
|
|
646
592
|
this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
|
|
@@ -657,7 +603,6 @@ export class Frontend extends EventEmitter {
|
|
|
657
603
|
res.status(500).send(`Error uploading or installing plugin package ${filename}`);
|
|
658
604
|
}
|
|
659
605
|
});
|
|
660
|
-
// Fallback for routing (must be the last route)
|
|
661
606
|
this.expressApp.use((req, res) => {
|
|
662
607
|
this.log.debug(`The frontend sent ${req.url} method ${req.method}: sending index.html as fallback`);
|
|
663
608
|
res.sendFile(path.join(this.matterbridge.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -666,16 +611,13 @@ export class Frontend extends EventEmitter {
|
|
|
666
611
|
}
|
|
667
612
|
async stop() {
|
|
668
613
|
this.log.debug('Stopping the frontend...');
|
|
669
|
-
// Remove listeners from the express app
|
|
670
614
|
if (this.expressApp) {
|
|
671
615
|
this.expressApp.removeAllListeners();
|
|
672
616
|
this.expressApp = undefined;
|
|
673
617
|
this.log.debug('Frontend app closed successfully');
|
|
674
618
|
}
|
|
675
|
-
// Close the WebSocket server
|
|
676
619
|
if (this.webSocketServer) {
|
|
677
620
|
this.log.debug('Closing WebSocket server...');
|
|
678
|
-
// Close all active connections
|
|
679
621
|
this.webSocketServer.clients.forEach((client) => {
|
|
680
622
|
if (client.readyState === WebSocket.OPEN) {
|
|
681
623
|
client.close();
|
|
@@ -684,7 +626,6 @@ export class Frontend extends EventEmitter {
|
|
|
684
626
|
await withTimeout(new Promise((resolve) => {
|
|
685
627
|
this.webSocketServer?.close((error) => {
|
|
686
628
|
if (error) {
|
|
687
|
-
// istanbul ignore next
|
|
688
629
|
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
689
630
|
}
|
|
690
631
|
else {
|
|
@@ -697,27 +638,8 @@ export class Frontend extends EventEmitter {
|
|
|
697
638
|
this.webSocketServer.removeAllListeners();
|
|
698
639
|
this.webSocketServer = undefined;
|
|
699
640
|
}
|
|
700
|
-
// Close the http server
|
|
701
641
|
if (this.httpServer) {
|
|
702
642
|
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
643
|
this.httpServer.close();
|
|
722
644
|
this.log.debug('Http server closed successfully');
|
|
723
645
|
this.listening = false;
|
|
@@ -726,27 +648,8 @@ export class Frontend extends EventEmitter {
|
|
|
726
648
|
this.httpServer = undefined;
|
|
727
649
|
this.log.debug('Frontend http server closed successfully');
|
|
728
650
|
}
|
|
729
|
-
// Close the https server
|
|
730
651
|
if (this.httpsServer) {
|
|
731
652
|
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
653
|
this.httpsServer.close();
|
|
751
654
|
this.log.debug('Https server closed successfully');
|
|
752
655
|
this.listening = false;
|
|
@@ -757,70 +660,53 @@ export class Frontend extends EventEmitter {
|
|
|
757
660
|
}
|
|
758
661
|
this.log.debug('Frontend stopped successfully');
|
|
759
662
|
}
|
|
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
663
|
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()));
|
|
664
|
+
this.matterbridge.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
665
|
+
this.matterbridge.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
666
|
+
this.matterbridge.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
667
|
+
this.matterbridge.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
|
|
799
668
|
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
|
-
|
|
669
|
+
this.matterbridge.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
670
|
+
this.matterbridge.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
671
|
+
this.matterbridge.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
672
|
+
const info = {
|
|
673
|
+
homeDirectory: this.matterbridge.homeDirectory,
|
|
674
|
+
rootDirectory: this.matterbridge.rootDirectory,
|
|
675
|
+
matterbridgeDirectory: this.matterbridge.matterbridgeDirectory,
|
|
676
|
+
matterbridgePluginDirectory: this.matterbridge.matterbridgePluginDirectory,
|
|
677
|
+
matterbridgeCertDirectory: this.matterbridge.matterbridgeCertDirectory,
|
|
678
|
+
globalModulesDirectory: this.matterbridge.globalModulesDirectory,
|
|
679
|
+
matterbridgeVersion: this.matterbridge.matterbridgeVersion,
|
|
680
|
+
matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
|
|
681
|
+
matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
|
|
682
|
+
frontendVersion: this.matterbridge.frontendVersion,
|
|
683
|
+
bridgeMode: this.matterbridge.bridgeMode,
|
|
684
|
+
restartMode: this.matterbridge.restartMode,
|
|
685
|
+
virtualMode: this.matterbridge.virtualMode,
|
|
686
|
+
profile: this.matterbridge.profile,
|
|
687
|
+
readOnly: this.matterbridge.readOnly,
|
|
688
|
+
shellyBoard: this.matterbridge.shellyBoard,
|
|
689
|
+
shellySysUpdate: this.matterbridge.shellySysUpdate,
|
|
690
|
+
shellyMainUpdate: this.matterbridge.shellyMainUpdate,
|
|
691
|
+
loggerLevel: await this.matterbridge.getLogLevel(),
|
|
692
|
+
fileLogger: this.matterbridge.fileLogger,
|
|
693
|
+
matterLoggerLevel: Logger.level,
|
|
694
|
+
matterFileLogger: this.matterbridge.matterFileLogger,
|
|
695
|
+
matterMdnsInterface: this.matterbridge.mdnsInterface,
|
|
696
|
+
matterIpv4Address: this.matterbridge.ipv4Address,
|
|
697
|
+
matterIpv6Address: this.matterbridge.ipv6Address,
|
|
698
|
+
matterPort: (await this.matterbridge.nodeContext?.get('matterport', 5540)) ?? 5540,
|
|
699
|
+
matterDiscriminator: await this.matterbridge.nodeContext?.get('matterdiscriminator'),
|
|
700
|
+
matterPasscode: await this.matterbridge.nodeContext?.get('matterpasscode'),
|
|
701
|
+
restartRequired: this.matterbridge.restartRequired,
|
|
702
|
+
fixedRestartRequired: this.matterbridge.fixedRestartRequired,
|
|
703
|
+
updateRequired: this.matterbridge.updateRequired,
|
|
704
|
+
};
|
|
705
|
+
return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
|
|
816
706
|
}
|
|
817
|
-
/**
|
|
818
|
-
* Retrieves the reachable attribute.
|
|
819
|
-
*
|
|
820
|
-
* @param {MatterbridgeEndpoint} device - The MatterbridgeEndpoint object.
|
|
821
|
-
* @returns {boolean} The reachable attribute.
|
|
822
|
-
*/
|
|
823
707
|
getReachability(device) {
|
|
708
|
+
if (this.matterbridge.hasCleanupStarted)
|
|
709
|
+
return false;
|
|
824
710
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
825
711
|
return false;
|
|
826
712
|
if (device.hasClusterServer(BridgedDeviceBasicInformation.Cluster.id))
|
|
@@ -831,13 +717,9 @@ export class Frontend extends EventEmitter {
|
|
|
831
717
|
return true;
|
|
832
718
|
return false;
|
|
833
719
|
}
|
|
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
720
|
getPowerSource(endpoint) {
|
|
721
|
+
if (this.matterbridge.hasCleanupStarted)
|
|
722
|
+
return;
|
|
841
723
|
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
842
724
|
return undefined;
|
|
843
725
|
const powerSource = (device) => {
|
|
@@ -852,23 +734,16 @@ export class Frontend extends EventEmitter {
|
|
|
852
734
|
}
|
|
853
735
|
return;
|
|
854
736
|
};
|
|
855
|
-
// Root endpoint
|
|
856
737
|
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
857
738
|
return powerSource(endpoint);
|
|
858
|
-
// Child endpoints
|
|
859
739
|
for (const child of endpoint.getChildEndpoints()) {
|
|
860
740
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
861
741
|
return powerSource(child);
|
|
862
742
|
}
|
|
863
743
|
}
|
|
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
744
|
getClusterTextFromDevice(device) {
|
|
745
|
+
if (this.matterbridge.hasCleanupStarted)
|
|
746
|
+
return '';
|
|
872
747
|
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
873
748
|
return '';
|
|
874
749
|
const getUserLabel = (device) => {
|
|
@@ -878,7 +753,6 @@ export class Frontend extends EventEmitter {
|
|
|
878
753
|
if (composed)
|
|
879
754
|
return 'Composed: ' + composed.value;
|
|
880
755
|
}
|
|
881
|
-
// istanbul ignore next cause is not reachable
|
|
882
756
|
return '';
|
|
883
757
|
};
|
|
884
758
|
const getFixedLabel = (device) => {
|
|
@@ -888,13 +762,11 @@ export class Frontend extends EventEmitter {
|
|
|
888
762
|
if (composed)
|
|
889
763
|
return 'Composed: ' + composed.value;
|
|
890
764
|
}
|
|
891
|
-
// istanbul ignore next cause is not reacheable
|
|
892
765
|
return '';
|
|
893
766
|
};
|
|
894
767
|
let attributes = '';
|
|
895
768
|
let supportedModes = [];
|
|
896
769
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
897
|
-
// console.log(`${device.deviceName} => Cluster: ${clusterName}-${clusterId} Attribute: ${attributeName}-${attributeId} Value(${typeof attributeValue}): ${attributeValue}`);
|
|
898
770
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
899
771
|
return;
|
|
900
772
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
@@ -984,20 +856,14 @@ export class Frontend extends EventEmitter {
|
|
|
984
856
|
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
985
857
|
attributes += `${getUserLabel(device)} `;
|
|
986
858
|
});
|
|
987
|
-
// console.log(`${device.deviceName}.forEachAttribute: ${attributes}`);
|
|
988
859
|
return attributes.trimStart().trimEnd();
|
|
989
860
|
}
|
|
990
|
-
/**
|
|
991
|
-
* Retrieves the registered plugins sanitized for res.json().
|
|
992
|
-
*
|
|
993
|
-
* @returns {BaseRegisteredPlugin[]} An array of BaseRegisteredPlugin.
|
|
994
|
-
*/
|
|
995
861
|
getPlugins() {
|
|
996
862
|
if (this.matterbridge.hasCleanupStarted)
|
|
997
|
-
return [];
|
|
998
|
-
const
|
|
999
|
-
for (const plugin of this.matterbridge.plugins) {
|
|
1000
|
-
|
|
863
|
+
return [];
|
|
864
|
+
const plugins = [];
|
|
865
|
+
for (const plugin of this.matterbridge.plugins.array()) {
|
|
866
|
+
plugins.push({
|
|
1001
867
|
path: plugin.path,
|
|
1002
868
|
type: plugin.type,
|
|
1003
869
|
name: plugin.name,
|
|
@@ -1022,27 +888,18 @@ export class Frontend extends EventEmitter {
|
|
|
1022
888
|
schemaJson: plugin.schemaJson,
|
|
1023
889
|
hasWhiteList: plugin.configJson?.whiteList !== undefined,
|
|
1024
890
|
hasBlackList: plugin.configJson?.blackList !== undefined,
|
|
1025
|
-
// Childbridge mode specific data
|
|
1026
891
|
matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
|
|
1027
892
|
});
|
|
1028
893
|
}
|
|
1029
|
-
return
|
|
894
|
+
return plugins;
|
|
1030
895
|
}
|
|
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
896
|
getDevices(pluginName) {
|
|
1038
897
|
if (this.matterbridge.hasCleanupStarted)
|
|
1039
|
-
return [];
|
|
898
|
+
return [];
|
|
1040
899
|
const devices = [];
|
|
1041
900
|
for (const device of this.matterbridge.devices.array()) {
|
|
1042
|
-
// Filter by pluginName if provided
|
|
1043
901
|
if (pluginName && pluginName !== device.plugin)
|
|
1044
902
|
continue;
|
|
1045
|
-
// Check if the device has the required properties
|
|
1046
903
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
1047
904
|
continue;
|
|
1048
905
|
devices.push({
|
|
@@ -1062,39 +919,24 @@ export class Frontend extends EventEmitter {
|
|
|
1062
919
|
}
|
|
1063
920
|
return devices;
|
|
1064
921
|
}
|
|
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
922
|
getClusters(pluginName, endpointNumber) {
|
|
1075
923
|
if (this.matterbridge.hasCleanupStarted)
|
|
1076
|
-
return;
|
|
924
|
+
return;
|
|
1077
925
|
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
1078
926
|
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1079
927
|
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1080
928
|
return;
|
|
1081
929
|
}
|
|
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
930
|
const deviceTypes = [];
|
|
1085
931
|
const clusters = [];
|
|
1086
932
|
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1087
933
|
deviceTypes.push(d.deviceType);
|
|
1088
934
|
});
|
|
1089
|
-
// Get the clusters from the main endpoint
|
|
1090
935
|
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1091
936
|
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1092
937
|
return;
|
|
1093
938
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1094
939
|
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
940
|
clusters.push({
|
|
1099
941
|
endpoint: endpoint.number.toString(),
|
|
1100
942
|
number: endpoint.number,
|
|
@@ -1108,19 +950,12 @@ export class Frontend extends EventEmitter {
|
|
|
1108
950
|
attributeLocalValue: attributeValue,
|
|
1109
951
|
});
|
|
1110
952
|
});
|
|
1111
|
-
// Get the child endpoints
|
|
1112
953
|
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
954
|
childEndpoints.forEach((childEndpoint) => {
|
|
1117
|
-
// istanbul ignore if cause is not reachable: should never happen but ...
|
|
1118
955
|
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1119
956
|
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1120
957
|
return;
|
|
1121
958
|
}
|
|
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
959
|
const deviceTypes = [];
|
|
1125
960
|
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1126
961
|
deviceTypes.push(d.deviceType);
|
|
@@ -1130,9 +965,6 @@ export class Frontend extends EventEmitter {
|
|
|
1130
965
|
return;
|
|
1131
966
|
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1132
967
|
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
968
|
clusters.push({
|
|
1137
969
|
endpoint: childEndpoint.number.toString(),
|
|
1138
970
|
number: childEndpoint.number,
|
|
@@ -1149,13 +981,6 @@ export class Frontend extends EventEmitter {
|
|
|
1149
981
|
});
|
|
1150
982
|
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
|
|
1151
983
|
}
|
|
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
984
|
async wsMessageHandler(client, message) {
|
|
1160
985
|
let data;
|
|
1161
986
|
const sendResponse = (data) => {
|
|
@@ -1175,7 +1000,7 @@ export class Frontend extends EventEmitter {
|
|
|
1175
1000
|
};
|
|
1176
1001
|
try {
|
|
1177
1002
|
data = JSON.parse(message.toString());
|
|
1178
|
-
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method)
|
|
1003
|
+
if (!isValidNumber(data.id) || !isValidString(data.dst) || !isValidString(data.src) || !isValidString(data.method) || data.dst !== 'Matterbridge') {
|
|
1179
1004
|
this.log.error(`Invalid message from websocket client: ${debugStringify(data)}`);
|
|
1180
1005
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid message' });
|
|
1181
1006
|
return;
|
|
@@ -1204,111 +1029,24 @@ export class Frontend extends EventEmitter {
|
|
|
1204
1029
|
}
|
|
1205
1030
|
}
|
|
1206
1031
|
else if (data.method === '/api/install') {
|
|
1207
|
-
|
|
1208
|
-
|
|
1032
|
+
if (isValidString(data.params.packageName, 14) && isValidBoolean(data.params.restart)) {
|
|
1033
|
+
this.wssSendSnackbarMessage(`Installing package ${data.params.packageName}...`, 0);
|
|
1034
|
+
this.server.request({ type: 'plugins_install', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
|
|
1035
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1209
1038
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/install' });
|
|
1210
|
-
return;
|
|
1211
1039
|
}
|
|
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
1040
|
}
|
|
1280
1041
|
else if (data.method === '/api/uninstall') {
|
|
1281
|
-
|
|
1282
|
-
|
|
1042
|
+
if (isValidString(data.params.packageName, 14)) {
|
|
1043
|
+
this.wssSendSnackbarMessage(`Uninstalling package ${data.params.packageName}...`, 0);
|
|
1044
|
+
this.server.request({ type: 'plugins_uninstall', src: this.server.name, dst: 'plugins', params: { packageName: data.params.packageName } });
|
|
1045
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1283
1048
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter packageName in /api/uninstall' });
|
|
1284
|
-
return;
|
|
1285
1049
|
}
|
|
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
1050
|
}
|
|
1313
1051
|
else if (data.method === '/api/addplugin') {
|
|
1314
1052
|
const localData = data;
|
|
@@ -1336,7 +1074,6 @@ export class Frontend extends EventEmitter {
|
|
|
1336
1074
|
return;
|
|
1337
1075
|
})
|
|
1338
1076
|
.catch((_error) => {
|
|
1339
|
-
//
|
|
1340
1077
|
});
|
|
1341
1078
|
}
|
|
1342
1079
|
else {
|
|
@@ -1384,7 +1121,6 @@ export class Frontend extends EventEmitter {
|
|
|
1384
1121
|
return;
|
|
1385
1122
|
})
|
|
1386
1123
|
.catch((_error) => {
|
|
1387
|
-
//
|
|
1388
1124
|
});
|
|
1389
1125
|
}
|
|
1390
1126
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1410,7 +1146,6 @@ export class Frontend extends EventEmitter {
|
|
|
1410
1146
|
const plugin = this.matterbridge.plugins.get(data.params.pluginName);
|
|
1411
1147
|
await this.matterbridge.plugins.shutdown(plugin, 'The plugin is restarting.', false, true);
|
|
1412
1148
|
if (plugin.serverNode) {
|
|
1413
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1414
1149
|
await this.matterbridge.stopServerNode(plugin.serverNode);
|
|
1415
1150
|
plugin.serverNode = undefined;
|
|
1416
1151
|
}
|
|
@@ -1420,20 +1155,18 @@ export class Frontend extends EventEmitter {
|
|
|
1420
1155
|
this.matterbridge.devices.remove(device);
|
|
1421
1156
|
}
|
|
1422
1157
|
}
|
|
1423
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1424
1158
|
if (plugin.type === 'DynamicPlatform' && !plugin.locked)
|
|
1425
1159
|
await this.matterbridge.createDynamicPlugin(plugin);
|
|
1426
1160
|
await this.matterbridge.plugins.load(plugin, true, 'The plugin has been restarted', true);
|
|
1427
|
-
plugin.restartRequired = false;
|
|
1161
|
+
plugin.restartRequired = false;
|
|
1428
1162
|
let needRestart = 0;
|
|
1429
1163
|
for (const plugin of this.matterbridge.plugins) {
|
|
1430
1164
|
if (plugin.restartRequired)
|
|
1431
1165
|
needRestart++;
|
|
1432
1166
|
}
|
|
1433
1167
|
if (needRestart === 0) {
|
|
1434
|
-
this.wssSendRestartNotRequired(true);
|
|
1168
|
+
this.wssSendRestartNotRequired(true);
|
|
1435
1169
|
}
|
|
1436
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1437
1170
|
if (plugin.serverNode)
|
|
1438
1171
|
await this.matterbridge.startServerNode(plugin.serverNode);
|
|
1439
1172
|
this.wssSendSnackbarMessage(`Restarted plugin ${data.params.pluginName}`, 5, 'success');
|
|
@@ -1586,11 +1319,11 @@ export class Frontend extends EventEmitter {
|
|
|
1586
1319
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: await this.getApiSettings() });
|
|
1587
1320
|
}
|
|
1588
1321
|
else if (data.method === '/api/plugins') {
|
|
1589
|
-
const plugins = this.getPlugins();
|
|
1322
|
+
const plugins = this.matterbridge.hasCleanupStarted ? [] : this.getPlugins();
|
|
1590
1323
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
|
|
1591
1324
|
}
|
|
1592
1325
|
else if (data.method === '/api/devices') {
|
|
1593
|
-
const devices = this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
1326
|
+
const devices = this.matterbridge.hasCleanupStarted ? [] : this.getDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
1594
1327
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
|
|
1595
1328
|
}
|
|
1596
1329
|
else if (data.method === '/api/clusters') {
|
|
@@ -1690,22 +1423,22 @@ export class Frontend extends EventEmitter {
|
|
|
1690
1423
|
if (isValidString(data.params.value, 4)) {
|
|
1691
1424
|
this.log.debug('Matterbridge logger level:', data.params.value);
|
|
1692
1425
|
if (data.params.value === 'Debug') {
|
|
1693
|
-
await this.matterbridge.setLogLevel("debug"
|
|
1426
|
+
await this.matterbridge.setLogLevel("debug");
|
|
1694
1427
|
}
|
|
1695
1428
|
else if (data.params.value === 'Info') {
|
|
1696
|
-
await this.matterbridge.setLogLevel("info"
|
|
1429
|
+
await this.matterbridge.setLogLevel("info");
|
|
1697
1430
|
}
|
|
1698
1431
|
else if (data.params.value === 'Notice') {
|
|
1699
|
-
await this.matterbridge.setLogLevel("notice"
|
|
1432
|
+
await this.matterbridge.setLogLevel("notice");
|
|
1700
1433
|
}
|
|
1701
1434
|
else if (data.params.value === 'Warn') {
|
|
1702
|
-
await this.matterbridge.setLogLevel("warn"
|
|
1435
|
+
await this.matterbridge.setLogLevel("warn");
|
|
1703
1436
|
}
|
|
1704
1437
|
else if (data.params.value === 'Error') {
|
|
1705
|
-
await this.matterbridge.setLogLevel("error"
|
|
1438
|
+
await this.matterbridge.setLogLevel("error");
|
|
1706
1439
|
}
|
|
1707
1440
|
else if (data.params.value === 'Fatal') {
|
|
1708
|
-
await this.matterbridge.setLogLevel("fatal"
|
|
1441
|
+
await this.matterbridge.setLogLevel("fatal");
|
|
1709
1442
|
}
|
|
1710
1443
|
await this.matterbridge.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
1711
1444
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1714,11 +1447,10 @@ export class Frontend extends EventEmitter {
|
|
|
1714
1447
|
case 'setmblogfile':
|
|
1715
1448
|
if (isValidBoolean(data.params.value)) {
|
|
1716
1449
|
this.log.debug('Matterbridge file log:', data.params.value);
|
|
1717
|
-
this.matterbridge.
|
|
1450
|
+
this.matterbridge.fileLogger = data.params.value;
|
|
1718
1451
|
await this.matterbridge.nodeContext?.set('matterbridgeFileLog', data.params.value);
|
|
1719
|
-
// Create the file logger for matterbridge
|
|
1720
1452
|
if (data.params.value)
|
|
1721
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.
|
|
1453
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), await this.matterbridge.getLogLevel(), true);
|
|
1722
1454
|
else
|
|
1723
1455
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
1724
1456
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
@@ -1745,7 +1477,6 @@ export class Frontend extends EventEmitter {
|
|
|
1745
1477
|
else if (data.params.value === 'Fatal') {
|
|
1746
1478
|
Logger.level = MatterLogLevel.FATAL;
|
|
1747
1479
|
}
|
|
1748
|
-
this.matterbridge.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
1749
1480
|
await this.matterbridge.nodeContext?.set('matterLogLevel', Logger.level);
|
|
1750
1481
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1751
1482
|
}
|
|
@@ -1753,7 +1484,7 @@ export class Frontend extends EventEmitter {
|
|
|
1753
1484
|
case 'setmjlogfile':
|
|
1754
1485
|
if (isValidBoolean(data.params.value)) {
|
|
1755
1486
|
this.log.debug('Matter file log:', data.params.value);
|
|
1756
|
-
this.matterbridge.
|
|
1487
|
+
this.matterbridge.matterFileLogger = data.params.value;
|
|
1757
1488
|
await this.matterbridge.nodeContext?.set('matterFileLog', data.params.value);
|
|
1758
1489
|
if (data.params.value) {
|
|
1759
1490
|
this.matterbridge.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
@@ -1768,89 +1499,92 @@ export class Frontend extends EventEmitter {
|
|
|
1768
1499
|
if (isValidString(data.params.value)) {
|
|
1769
1500
|
this.log.debug(`Matter.js mdns interface: ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
|
|
1770
1501
|
this.matterbridge.mdnsInterface = data.params.value === '' ? undefined : data.params.value;
|
|
1771
|
-
this.matterbridge.matterbridgeInformation.matterMdnsInterface = this.matterbridge.mdnsInterface;
|
|
1772
1502
|
await this.matterbridge.nodeContext?.set('mattermdnsinterface', data.params.value);
|
|
1773
1503
|
this.wssSendRestartRequired();
|
|
1774
1504
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1505
|
+
this.wssSendSnackbarMessage(`Mdns interface changed to ${data.params.value === '' ? 'all interfaces' : data.params.value}`);
|
|
1775
1506
|
}
|
|
1776
1507
|
break;
|
|
1777
1508
|
case 'setipv4address':
|
|
1778
1509
|
if (isValidString(data.params.value)) {
|
|
1779
1510
|
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;
|
|
1511
|
+
this.matterbridge.ipv4Address = data.params.value === '' ? undefined : data.params.value;
|
|
1782
1512
|
await this.matterbridge.nodeContext?.set('matteripv4address', data.params.value);
|
|
1783
1513
|
this.wssSendRestartRequired();
|
|
1784
1514
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1515
|
+
this.wssSendSnackbarMessage(`IPv4 address changed to ${data.params.value === '' ? 'all ipv4 addresses' : data.params.value}`);
|
|
1785
1516
|
}
|
|
1786
1517
|
break;
|
|
1787
1518
|
case 'setipv6address':
|
|
1788
1519
|
if (isValidString(data.params.value)) {
|
|
1789
1520
|
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;
|
|
1521
|
+
this.matterbridge.ipv6Address = data.params.value === '' ? undefined : data.params.value;
|
|
1792
1522
|
await this.matterbridge.nodeContext?.set('matteripv6address', data.params.value);
|
|
1793
1523
|
this.wssSendRestartRequired();
|
|
1794
1524
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1525
|
+
this.wssSendSnackbarMessage(`IPv6 address changed to ${data.params.value === '' ? 'all ipv6 addresses' : data.params.value}`);
|
|
1795
1526
|
}
|
|
1796
1527
|
break;
|
|
1797
1528
|
case 'setmatterport':
|
|
1798
|
-
// eslint-disable-next-line no-case-declarations
|
|
1799
1529
|
const port = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1800
1530
|
if (isValidNumber(port, 5540, 5600)) {
|
|
1801
1531
|
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
1802
|
-
this.matterbridge.
|
|
1532
|
+
this.matterbridge.port = port;
|
|
1803
1533
|
await this.matterbridge.nodeContext?.set('matterport', port);
|
|
1804
1534
|
this.wssSendRestartRequired();
|
|
1805
1535
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1536
|
+
this.wssSendSnackbarMessage(`Matter port changed to ${port}`);
|
|
1806
1537
|
}
|
|
1807
1538
|
else {
|
|
1808
1539
|
this.log.debug(`Reset matter commissioning port to ${CYAN}5540${db}`);
|
|
1809
|
-
this.matterbridge.
|
|
1540
|
+
this.matterbridge.port = 5540;
|
|
1810
1541
|
await this.matterbridge.nodeContext?.set('matterport', 5540);
|
|
1811
1542
|
this.wssSendRestartRequired();
|
|
1812
1543
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning port to default 5540' });
|
|
1544
|
+
this.wssSendSnackbarMessage(`Matter port reset to default 5540`, undefined, 'error');
|
|
1813
1545
|
}
|
|
1814
1546
|
break;
|
|
1815
1547
|
case 'setmatterdiscriminator':
|
|
1816
|
-
// eslint-disable-next-line no-case-declarations
|
|
1817
1548
|
const discriminator = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1818
1549
|
if (isValidNumber(discriminator, 0, 4095)) {
|
|
1819
1550
|
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
1820
|
-
this.matterbridge.
|
|
1551
|
+
this.matterbridge.discriminator = discriminator;
|
|
1821
1552
|
await this.matterbridge.nodeContext?.set('matterdiscriminator', discriminator);
|
|
1822
1553
|
this.wssSendRestartRequired();
|
|
1823
1554
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1555
|
+
this.wssSendSnackbarMessage(`Matter discriminator changed to ${discriminator}`);
|
|
1824
1556
|
}
|
|
1825
1557
|
else {
|
|
1826
1558
|
this.log.debug(`Reset matter commissioning discriminator to ${CYAN}undefined${db}`);
|
|
1827
|
-
this.matterbridge.
|
|
1559
|
+
this.matterbridge.discriminator = undefined;
|
|
1828
1560
|
await this.matterbridge.nodeContext?.remove('matterdiscriminator');
|
|
1829
1561
|
this.wssSendRestartRequired();
|
|
1830
1562
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning discriminator to default undefined' });
|
|
1563
|
+
this.wssSendSnackbarMessage(`Matter discriminator reset to default`, undefined, 'error');
|
|
1831
1564
|
}
|
|
1832
1565
|
break;
|
|
1833
1566
|
case 'setmatterpasscode':
|
|
1834
|
-
// eslint-disable-next-line no-case-declarations
|
|
1835
1567
|
const passcode = isValidString(data.params.value) ? parseInt(data.params.value) : 0;
|
|
1836
1568
|
if (isValidNumber(passcode, 1, 99999998) && CommissioningOptions.FORBIDDEN_PASSCODES.includes(passcode) === false) {
|
|
1837
|
-
this.matterbridge.
|
|
1569
|
+
this.matterbridge.passcode = passcode;
|
|
1838
1570
|
this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
|
|
1839
1571
|
await this.matterbridge.nodeContext?.set('matterpasscode', passcode);
|
|
1840
1572
|
this.wssSendRestartRequired();
|
|
1841
1573
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
|
|
1574
|
+
this.wssSendSnackbarMessage(`Matter passcode changed to ${passcode}`);
|
|
1842
1575
|
}
|
|
1843
1576
|
else {
|
|
1844
1577
|
this.log.debug(`Reset matter commissioning passcode to ${CYAN}undefined${db}`);
|
|
1845
|
-
this.matterbridge.
|
|
1578
|
+
this.matterbridge.passcode = undefined;
|
|
1846
1579
|
await this.matterbridge.nodeContext?.remove('matterpasscode');
|
|
1847
1580
|
this.wssSendRestartRequired();
|
|
1848
1581
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Invalid value: reset matter commissioning passcode to default undefined' });
|
|
1582
|
+
this.wssSendSnackbarMessage(`Matter passcode reset to default`, undefined, 'error');
|
|
1849
1583
|
}
|
|
1850
1584
|
break;
|
|
1851
1585
|
case 'setvirtualmode':
|
|
1852
1586
|
if (isValidString(data.params.value, 1) && ['disabled', 'light', 'outlet', 'switch', 'mounted_switch'].includes(data.params.value)) {
|
|
1853
|
-
this.matterbridge.
|
|
1587
|
+
this.matterbridge.virtualMode = data.params.value;
|
|
1854
1588
|
this.log.debug(`Set matterbridge virtual mode to ${CYAN}${data.params.value}${db}`);
|
|
1855
1589
|
await this.matterbridge.nodeContext?.set('virtualmode', data.params.value);
|
|
1856
1590
|
this.wssSendRestartRequired();
|
|
@@ -1875,19 +1609,15 @@ export class Frontend extends EventEmitter {
|
|
|
1875
1609
|
return;
|
|
1876
1610
|
}
|
|
1877
1611
|
const config = plugin.configJson;
|
|
1878
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1879
1612
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1880
|
-
// this.log.debug(`SelectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1881
1613
|
if (select === 'serial')
|
|
1882
1614
|
this.log.info(`Selected device serial ${data.params.serial}`);
|
|
1883
1615
|
if (select === 'name')
|
|
1884
1616
|
this.log.info(`Selected device name ${data.params.name}`);
|
|
1885
1617
|
if (config && select && (select === 'serial' || select === 'name')) {
|
|
1886
|
-
// Remove postfix from the serial if it exists
|
|
1887
1618
|
if (config.postfix) {
|
|
1888
1619
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1889
1620
|
}
|
|
1890
|
-
// Add the serial to the whiteList if the whiteList exists and the serial or name is not already in it
|
|
1891
1621
|
if (isValidArray(config.whiteList, 1)) {
|
|
1892
1622
|
if (select === 'serial' && !config.whiteList.includes(data.params.serial)) {
|
|
1893
1623
|
config.whiteList.push(data.params.serial);
|
|
@@ -1896,7 +1626,6 @@ export class Frontend extends EventEmitter {
|
|
|
1896
1626
|
config.whiteList.push(data.params.name);
|
|
1897
1627
|
}
|
|
1898
1628
|
}
|
|
1899
|
-
// Remove the serial from the blackList if the blackList exists and the serial or name is in it
|
|
1900
1629
|
if (isValidArray(config.blackList, 1)) {
|
|
1901
1630
|
if (select === 'serial' && config.blackList.includes(data.params.serial)) {
|
|
1902
1631
|
config.blackList = config.blackList.filter((item) => item !== localData.params.serial);
|
|
@@ -1924,9 +1653,7 @@ export class Frontend extends EventEmitter {
|
|
|
1924
1653
|
return;
|
|
1925
1654
|
}
|
|
1926
1655
|
const config = plugin.configJson;
|
|
1927
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1928
1656
|
const select = plugin.schemaJson?.properties?.blackList?.selectFrom;
|
|
1929
|
-
// this.log.debug(`UnselectDevice(selectMode ${select}) data ${debugStringify(data)}`);
|
|
1930
1657
|
if (select === 'serial')
|
|
1931
1658
|
this.log.info(`Unselected device serial ${data.params.serial}`);
|
|
1932
1659
|
if (select === 'name')
|
|
@@ -1935,7 +1662,6 @@ export class Frontend extends EventEmitter {
|
|
|
1935
1662
|
if (config.postfix) {
|
|
1936
1663
|
data.params.serial = data.params.serial.replace('-' + config.postfix, '');
|
|
1937
1664
|
}
|
|
1938
|
-
// Remove the serial from the whiteList if the whiteList exists and the serial is in it
|
|
1939
1665
|
if (isValidArray(config.whiteList, 1)) {
|
|
1940
1666
|
if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
|
|
1941
1667
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.serial);
|
|
@@ -1944,7 +1670,6 @@ export class Frontend extends EventEmitter {
|
|
|
1944
1670
|
config.whiteList = config.whiteList.filter((item) => item !== localData.params.name);
|
|
1945
1671
|
}
|
|
1946
1672
|
}
|
|
1947
|
-
// Add the serial to the blackList
|
|
1948
1673
|
if (isValidArray(config.blackList)) {
|
|
1949
1674
|
if (select === 'serial' && !config.blackList.includes(data.params.serial)) {
|
|
1950
1675
|
config.blackList.push(data.params.serial);
|
|
@@ -1967,7 +1692,6 @@ export class Frontend extends EventEmitter {
|
|
|
1967
1692
|
}
|
|
1968
1693
|
}
|
|
1969
1694
|
else {
|
|
1970
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1971
1695
|
const localData = data;
|
|
1972
1696
|
this.log.error(`Invalid method from websocket client: ${debugStringify(localData)}`);
|
|
1973
1697
|
sendResponse({ id: localData.id, method: localData.method, src: 'Matterbridge', dst: localData.src, error: 'Invalid method' });
|
|
@@ -1977,44 +1701,21 @@ export class Frontend extends EventEmitter {
|
|
|
1977
1701
|
inspectError(this.log, `Error processing message "${message}" from websocket client`, error);
|
|
1978
1702
|
}
|
|
1979
1703
|
}
|
|
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
1704
|
wssSendLogMessage(level, time, name, message) {
|
|
1994
1705
|
if (!level || !time || !name || !message)
|
|
1995
1706
|
return;
|
|
1996
|
-
// Remove ANSI escape codes from the message
|
|
1997
|
-
// eslint-disable-next-line no-control-regex
|
|
1998
1707
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
1999
|
-
// Remove leading asterisks from the message
|
|
2000
1708
|
message = message.replace(/^\*+/, '');
|
|
2001
|
-
// Replace all occurrences of \t and \n
|
|
2002
1709
|
message = message.replace(/[\t\n]/g, '');
|
|
2003
|
-
// Remove non-printable characters
|
|
2004
|
-
// eslint-disable-next-line no-control-regex
|
|
2005
1710
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2006
|
-
// Replace all occurrences of \" with "
|
|
2007
1711
|
message = message.replace(/\\"/g, '"');
|
|
2008
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2009
1712
|
const maxContinuousLength = 100;
|
|
2010
1713
|
const keepStartLength = 20;
|
|
2011
1714
|
const keepEndLength = 20;
|
|
2012
|
-
// Split the message into words
|
|
2013
1715
|
if (level !== 'spawn') {
|
|
2014
1716
|
message = message
|
|
2015
1717
|
.split(' ')
|
|
2016
1718
|
.map((word) => {
|
|
2017
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2018
1719
|
if (word.length > maxContinuousLength) {
|
|
2019
1720
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2020
1721
|
}
|
|
@@ -2022,161 +1723,60 @@ export class Frontend extends EventEmitter {
|
|
|
2022
1723
|
})
|
|
2023
1724
|
.join(' ');
|
|
2024
1725
|
}
|
|
2025
|
-
// Send the message to all connected clients
|
|
2026
1726
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'log', success: true, response: { level, time, name, message } });
|
|
2027
1727
|
}
|
|
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
1728
|
wssSendRefreshRequired(changed, params) {
|
|
2041
1729
|
this.log.debug('Sending a refresh required message to all connected clients');
|
|
2042
|
-
// Send the message to all connected clients
|
|
2043
1730
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', success: true, response: { changed, ...params } });
|
|
2044
1731
|
}
|
|
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
1732
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
2052
1733
|
this.log.debug('Sending a restart required message to all connected clients');
|
|
2053
|
-
this.matterbridge.
|
|
2054
|
-
this.matterbridge.
|
|
1734
|
+
this.matterbridge.restartRequired = true;
|
|
1735
|
+
this.matterbridge.fixedRestartRequired = fixed;
|
|
2055
1736
|
if (snackbar === true)
|
|
2056
1737
|
this.wssSendSnackbarMessage(`Restart required`, 0);
|
|
2057
|
-
// Send the message to all connected clients
|
|
2058
1738
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', success: true, response: { fixed } });
|
|
2059
1739
|
}
|
|
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
1740
|
wssSendRestartNotRequired(snackbar = true) {
|
|
2066
1741
|
this.log.debug('Sending a restart not required message to all connected clients');
|
|
2067
|
-
this.matterbridge.
|
|
1742
|
+
this.matterbridge.restartRequired = false;
|
|
2068
1743
|
if (snackbar === true)
|
|
2069
1744
|
this.wssSendCloseSnackbarMessage(`Restart required`);
|
|
2070
|
-
// Send the message to all connected clients
|
|
2071
1745
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'restart_not_required', success: true });
|
|
2072
1746
|
}
|
|
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
1747
|
wssSendUpdateRequired(devVersion = false) {
|
|
2079
1748
|
this.log.debug('Sending an update required message to all connected clients');
|
|
2080
|
-
this.matterbridge.
|
|
2081
|
-
// Send the message to all connected clients
|
|
1749
|
+
this.matterbridge.updateRequired = true;
|
|
2082
1750
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'update_required', success: true, response: { devVersion } });
|
|
2083
1751
|
}
|
|
2084
|
-
/**
|
|
2085
|
-
* Sends a cpu update message to all connected clients.
|
|
2086
|
-
*
|
|
2087
|
-
* @param {number} cpuUsage - The CPU usage percentage to send.
|
|
2088
|
-
*/
|
|
2089
1752
|
wssSendCpuUpdate(cpuUsage) {
|
|
2090
1753
|
if (hasParameter('debug'))
|
|
2091
1754
|
this.log.debug('Sending a cpu update message to all connected clients');
|
|
2092
|
-
// Send the message to all connected clients
|
|
2093
1755
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'cpu_update', success: true, response: { cpuUsage: Math.round(cpuUsage * 100) / 100 } });
|
|
2094
1756
|
}
|
|
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
1757
|
wssSendMemoryUpdate(totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers) {
|
|
2107
1758
|
if (hasParameter('debug'))
|
|
2108
1759
|
this.log.debug('Sending a memory update message to all connected clients');
|
|
2109
|
-
// Send the message to all connected clients
|
|
2110
1760
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'memory_update', success: true, response: { totalMemory, freeMemory, rss, heapTotal, heapUsed, external, arrayBuffers } });
|
|
2111
1761
|
}
|
|
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
1762
|
wssSendUptimeUpdate(systemUptime, processUptime) {
|
|
2119
1763
|
if (hasParameter('debug'))
|
|
2120
1764
|
this.log.debug('Sending a uptime update message to all connected clients');
|
|
2121
|
-
// Send the message to all connected clients
|
|
2122
1765
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'uptime_update', success: true, response: { systemUptime, processUptime } });
|
|
2123
1766
|
}
|
|
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
1767
|
wssSendSnackbarMessage(message, timeout = 5, severity = 'info') {
|
|
2136
1768
|
this.log.debug('Sending a snackbar message to all connected clients');
|
|
2137
|
-
// Send the message to all connected clients
|
|
2138
1769
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'snackbar', success: true, response: { message, timeout, severity } });
|
|
2139
1770
|
}
|
|
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
1771
|
wssSendCloseSnackbarMessage(message) {
|
|
2147
1772
|
this.log.debug('Sending a close snackbar message to all connected clients');
|
|
2148
|
-
// Send the message to all connected clients
|
|
2149
1773
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'close_snackbar', success: true, response: { message } });
|
|
2150
1774
|
}
|
|
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
1775
|
wssSendAttributeChangedMessage(plugin, serialNumber, uniqueId, number, id, cluster, attribute, value) {
|
|
2168
1776
|
this.log.debug('Sending an attribute update message to all connected clients');
|
|
2169
|
-
// Send the message to all connected clients
|
|
2170
1777
|
this.wssBroadcastMessage({ id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'state_update', success: true, response: { plugin, serialNumber, uniqueId, number, id, cluster, attribute, value } });
|
|
2171
1778
|
}
|
|
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
1779
|
wssBroadcastMessage(msg) {
|
|
2179
|
-
// Send the message to all connected clients
|
|
2180
1780
|
const stringifiedMsg = JSON.stringify(msg);
|
|
2181
1781
|
if (msg.method !== 'log')
|
|
2182
1782
|
this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
|
|
@@ -2187,4 +1787,3 @@ export class Frontend extends EventEmitter {
|
|
|
2187
1787
|
});
|
|
2188
1788
|
}
|
|
2189
1789
|
}
|
|
2190
|
-
//# sourceMappingURL=frontend.js.map
|