homebridge-config-ui-x 5.9.0 → 5.9.1-beta.1
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 +20 -0
- package/dist/core/auth/auth.controller.d.ts +5 -0
- package/dist/core/config/config.interfaces.d.ts +13 -0
- package/dist/core/config/config.service.d.ts +9 -0
- package/dist/core/config/config.service.js +18 -0
- package/dist/core/config/config.service.js.map +1 -1
- package/dist/core/config/config.startup.js +48 -17
- package/dist/core/config/config.startup.js.map +1 -1
- package/dist/core/feature-flags/feature-flags.registry.js +5 -0
- package/dist/core/feature-flags/feature-flags.registry.js.map +1 -1
- package/dist/core/spa/spa-html.service.d.ts +5 -0
- package/dist/core/spa/spa-html.service.js +38 -0
- package/dist/core/spa/spa-html.service.js.map +1 -0
- package/dist/core/spa/spa.filter.d.ts +3 -0
- package/dist/core/spa/spa.filter.js +21 -2
- package/dist/core/spa/spa.filter.js.map +1 -1
- package/dist/core/ssl/ssl-cert-generator.service.d.ts +15 -0
- package/dist/core/ssl/ssl-cert-generator.service.js +127 -0
- package/dist/core/ssl/ssl-cert-generator.service.js.map +1 -0
- package/dist/globalDefaults.js +3 -0
- package/dist/globalDefaults.js.map +1 -1
- package/dist/main.js +18 -4
- package/dist/main.js.map +1 -1
- package/dist/modules/child-bridges/child-bridges.interfaces.d.ts +9 -0
- package/dist/modules/config-editor/config-editor.controller.d.ts +3 -0
- package/dist/modules/config-editor/config-editor.controller.js +45 -1
- package/dist/modules/config-editor/config-editor.controller.js.map +1 -1
- package/dist/modules/config-editor/config-editor.module.js +2 -0
- package/dist/modules/config-editor/config-editor.module.js.map +1 -1
- package/dist/modules/config-editor/config-editor.service.d.ts +8 -2
- package/dist/modules/config-editor/config-editor.service.js +46 -3
- package/dist/modules/config-editor/config-editor.service.js.map +1 -1
- package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.js +1 -1
- package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.js.map +1 -1
- package/dist/modules/plugins/plugins.service.js +17 -1
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/server/server.controller.d.ts +50 -0
- package/dist/modules/server/server.controller.js +200 -1
- package/dist/modules/server/server.controller.js.map +1 -1
- package/dist/modules/server/server.service.d.ts +48 -0
- package/dist/modules/server/server.service.js +536 -15
- package/dist/modules/server/server.service.js.map +1 -1
- package/dist/modules/status/status.gateway.d.ts +2 -0
- package/dist/modules/status/status.interfaces.d.ts +11 -0
- package/dist/modules/status/status.service.d.ts +4 -1
- package/dist/modules/status/status.service.js +21 -2
- package/dist/modules/status/status.service.js.map +1 -1
- package/docs/ssl-upload-pr.md +103 -0
- package/package.json +3 -1
- package/public/assets/matter.svg +8 -0
- package/public/assets/plugin-ui-utils/ui.js +3 -0
- package/public/assets/plugin-ui-utils/ui.js.map +1 -1
- package/public/{chunk-PK7EWRSF.js → chunk-2CNQY2E7.js} +1 -1
- package/public/{chunk-FJCVFWLW.js → chunk-2FNQEOXF.js} +1 -1
- package/public/{chunk-7F2HVPC5.js → chunk-2KEG7SYT.js} +1 -1
- package/public/{chunk-LRD25TWX.js → chunk-2LXNPXT5.js} +1 -1
- package/public/{chunk-U3JDCGCU.js → chunk-2UOYBCTN.js} +1 -1
- package/public/{chunk-5PCQVX7H.js → chunk-325LPX6W.js} +1 -1
- package/public/{chunk-EUGVBS4N.js → chunk-3E2NZ5JW.js} +1 -1
- package/public/{chunk-X3DENUZZ.js → chunk-3IJXQMYZ.js} +1 -1
- package/public/{chunk-UHOVCHON.js → chunk-3IZSIVEA.js} +1 -1
- package/public/{chunk-WHIRPY75.js → chunk-3XDYZNNE.js} +1 -1
- package/public/{chunk-75E7S6CO.js → chunk-4QXLJX7D.js} +1 -1
- package/public/{chunk-KW266TAV.js → chunk-57DUDNZJ.js} +1 -1
- package/public/{chunk-LWOAR6BY.js → chunk-5ETJWAIW.js} +1 -1
- package/public/{chunk-OQ5J7Z5A.js → chunk-5T4JHJYL.js} +1 -1
- package/public/{chunk-KX4SDDFZ.js → chunk-6KT3BYU6.js} +1 -1
- package/public/chunk-732XLY23.js +16 -0
- package/public/{chunk-3HKQMTY5.js → chunk-73TX7DHV.js} +1 -1
- package/public/{chunk-2JSU6A57.js → chunk-7IYTG6GP.js} +1 -1
- package/public/{chunk-IESBL4DA.js → chunk-7UDDRMZZ.js} +1 -1
- package/public/{chunk-YZT252N4.js → chunk-ADW6BF5G.js} +1 -1
- package/public/{chunk-MFEME63Q.js → chunk-AI6E5JVH.js} +1 -1
- package/public/{chunk-M4T2RO3I.js → chunk-ASKB5DLO.js} +1 -1
- package/public/{chunk-VB3KKHCI.js → chunk-BLT2YCDN.js} +1 -1
- package/public/chunk-BTBGWLKK.js +1 -0
- package/public/{chunk-N5GOQDTB.js → chunk-CYBDQV2B.js} +1 -1
- package/public/{chunk-DT6ICOAZ.js → chunk-D5RKKI2A.js} +1 -1
- package/public/{chunk-57P3RXT7.js → chunk-EAGKQ5OJ.js} +1 -1
- package/public/{chunk-CDA3O4BZ.js → chunk-EVND2DL5.js} +1 -1
- package/public/chunk-FM6ZYPKR.js +4 -0
- package/public/{chunk-MMLBIEDP.js → chunk-JJ7TVAIH.js} +4 -4
- package/public/{chunk-AYMJQ6UO.js → chunk-L43N56JA.js} +1 -1
- package/public/{chunk-3PJWBNGU.js → chunk-L5I3DZ23.js} +1 -1
- package/public/chunk-LH2LMHIZ.js +1 -0
- package/public/{chunk-CFEDPWK5.js → chunk-LWAL5JUG.js} +1 -1
- package/public/chunk-LZOHFRHN.js +1 -0
- package/public/{chunk-BJ7NEXKS.js → chunk-N2TWGDNX.js} +1 -1
- package/public/{chunk-WGFPD4J2.js → chunk-NKNWXFAK.js} +1 -1
- package/public/{chunk-7PXKC75G.js → chunk-O4SS7KVP.js} +1 -1
- package/public/{chunk-XYVHCMEV.js → chunk-OAHDGV32.js} +1 -1
- package/public/{chunk-DWKQU3C2.js → chunk-OK5EJ7US.js} +1 -1
- package/public/chunk-ONTEIYY3.js +19 -0
- package/public/{chunk-MYSGML5C.js → chunk-OS2SEJZU.js} +1 -1
- package/public/chunk-P7TZIGJS.js +50 -0
- package/public/{chunk-PYFU3YSX.js → chunk-Q2IS3QQY.js} +1 -1
- package/public/{chunk-QTJ3CQHI.js → chunk-QK4V5DVW.js} +1 -1
- package/public/chunk-QQ5VZZQG.js +1 -0
- package/public/{chunk-DRO3CNQN.js → chunk-RMZRXORR.js} +1 -1
- package/public/chunk-RYTGW6B7.js +1 -0
- package/public/chunk-SOT54YHI.js +1 -0
- package/public/{chunk-EV5RCPGM.js → chunk-SY4VNLXW.js} +1 -1
- package/public/chunk-TGZNYEGN.js +1 -0
- package/public/{chunk-TCYDYM5A.js → chunk-TOYVIOBH.js} +1 -1
- package/public/{chunk-CUP4YO7L.js → chunk-TSFGO3MA.js} +1 -1
- package/public/chunk-U5JF2ZOK.js +1 -0
- package/public/{chunk-IDDSROZF.js → chunk-UF2IM5BE.js} +1 -1
- package/public/{chunk-5R3NYRBJ.js → chunk-UNGEHXRN.js} +1 -1
- package/public/chunk-VEGMNMLG.js +1 -0
- package/public/{chunk-LTQGLD2K.js → chunk-VYC5JLE6.js} +1 -1
- package/public/{chunk-UGSDROFZ.js → chunk-W3SNJ7TC.js} +1 -1
- package/public/{chunk-5WH5JZQ5.js → chunk-WLGXJQPD.js} +1 -1
- package/public/{chunk-4FV7QIK7.js → chunk-WWSLIQVD.js} +1 -1
- package/public/{chunk-WZ6CFZR4.js → chunk-X5AKILHJ.js} +1 -1
- package/public/{chunk-6W43YCZG.js → chunk-Y5FP6C55.js} +1 -1
- package/public/{chunk-AOCOR5BO.js → chunk-YH6MRXGX.js} +1 -1
- package/public/{chunk-F2E7FMEJ.js → chunk-YNONXMOG.js} +1 -1
- package/public/{chunk-FW6YLROT.js → chunk-YYJG2ENF.js} +1 -1
- package/public/{chunk-X2HK2VP5.js → chunk-ZEI3HZ6P.js} +1 -1
- package/public/{chunk-ICEM4QHU.js → chunk-ZJJ3EOBP.js} +1 -1
- package/public/index.html +2 -2
- package/public/{main-OQMKHLDX.js → main-27RPBS22.js} +1 -1
- package/public/media/matter-P563JGDL.svg +8 -0
- package/public/{styles-7EFV5QBG.css → styles-CSF457UW.css} +1 -1
- package/scripts/extract-plugin-alias.js +53 -2
- package/public/chunk-3DANXKYW.js +0 -1
- package/public/chunk-4E3QJA2Q.js +0 -1
- package/public/chunk-BJ4JCAZT.js +0 -1
- package/public/chunk-BQ2T2C4E.js +0 -1
- package/public/chunk-CLRVHL2M.js +0 -1
- package/public/chunk-F72JFLBX.js +0 -1
- package/public/chunk-J7EVJFYY.js +0 -16
- package/public/chunk-MY3FPHNS.js +0 -1
- package/public/chunk-RSNBQILW.js +0 -4
- package/public/chunk-UBFASDE7.js +0 -1
- package/public/chunk-VLG3LTSC.js +0 -50
- package/public/chunk-X7FSVUGO.js +0 -19
- package/public/chunk-XYUTAICH.js +0 -1
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
2
18
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
19
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
20
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
21
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
22
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
23
|
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
8
41
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
42
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
43
|
};
|
|
@@ -15,9 +48,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
48
|
exports.ServerService = void 0;
|
|
16
49
|
const node_buffer_1 = require("node:buffer");
|
|
17
50
|
const node_child_process_1 = require("node:child_process");
|
|
51
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
52
|
const node_path_1 = require("node:path");
|
|
19
53
|
const node_process_1 = __importDefault(require("node:process"));
|
|
20
54
|
const node_stream_1 = require("node:stream");
|
|
55
|
+
const node_tls_1 = require("node:tls");
|
|
21
56
|
const node_util_1 = require("node:util");
|
|
22
57
|
const hap_types_1 = require("@homebridge/hap-client/dist/hap-types");
|
|
23
58
|
const common_1 = require("@nestjs/common");
|
|
@@ -28,6 +63,7 @@ const tcp_port_used_1 = require("tcp-port-used");
|
|
|
28
63
|
const config_service_1 = require("../../core/config/config.service");
|
|
29
64
|
const homebridge_ipc_service_1 = require("../../core/homebridge-ipc/homebridge-ipc.service");
|
|
30
65
|
const logger_service_1 = require("../../core/logger/logger.service");
|
|
66
|
+
const ssl_cert_generator_service_1 = require("../../core/ssl/ssl-cert-generator.service");
|
|
31
67
|
const accessories_service_1 = require("../accessories/accessories.service");
|
|
32
68
|
const config_editor_service_1 = require("../config-editor/config-editor.service");
|
|
33
69
|
const pump = (0, node_util_1.promisify)(node_stream_1.pipeline);
|
|
@@ -44,25 +80,37 @@ let ServerService = class ServerService {
|
|
|
44
80
|
this.accessoryId = this.configService.homebridgeConfig.bridge.username.split(':').join('');
|
|
45
81
|
this.accessoryInfoPath = (0, node_path_1.join)(this.configService.storagePath, 'persist', `AccessoryInfo.${this.accessoryId}.json`);
|
|
46
82
|
}
|
|
47
|
-
async deleteSingleDeviceAccessories(id, cachedAccessoriesDir) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await (0, fs_extra_1.
|
|
52
|
-
|
|
83
|
+
async deleteSingleDeviceAccessories(id, cachedAccessoriesDir, protocol = 'both') {
|
|
84
|
+
if (protocol === 'hap' || protocol === 'both') {
|
|
85
|
+
const cachedAccessories = (0, node_path_1.join)(cachedAccessoriesDir, `cachedAccessories.${id}`);
|
|
86
|
+
const cachedAccessoriesBackup = (0, node_path_1.join)(cachedAccessoriesDir, `.cachedAccessories.${id}.bak`);
|
|
87
|
+
if (await (0, fs_extra_1.pathExists)(cachedAccessories)) {
|
|
88
|
+
await (0, fs_extra_1.unlink)(cachedAccessories);
|
|
89
|
+
this.logger.warn(`Bridge ${id} HAP accessory removal: removed ${cachedAccessories}.`);
|
|
90
|
+
}
|
|
91
|
+
if (await (0, fs_extra_1.pathExists)(cachedAccessoriesBackup)) {
|
|
92
|
+
await (0, fs_extra_1.unlink)(cachedAccessoriesBackup);
|
|
93
|
+
this.logger.warn(`Bridge ${id} HAP accessory removal: removed ${cachedAccessoriesBackup}.`);
|
|
94
|
+
}
|
|
53
95
|
}
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
this.
|
|
96
|
+
if (protocol === 'matter' || protocol === 'both') {
|
|
97
|
+
const deviceId = id.split(':').join('').toUpperCase();
|
|
98
|
+
const matterPath = (0, node_path_1.join)(this.configService.storagePath, 'matter', deviceId);
|
|
99
|
+
if (await (0, fs_extra_1.pathExists)(matterPath)) {
|
|
100
|
+
await (0, fs_extra_1.remove)(matterPath);
|
|
101
|
+
this.logger.warn(`Bridge ${id} Matter accessory removal: removed Matter bridge storage at ${matterPath}.`);
|
|
102
|
+
}
|
|
57
103
|
}
|
|
58
104
|
}
|
|
59
105
|
async deleteSingleDevicePairing(id, resetPairingInfo) {
|
|
60
106
|
const persistPath = (0, node_path_1.join)(this.configService.storagePath, 'persist');
|
|
61
107
|
const accessoryInfo = (0, node_path_1.join)(persistPath, `AccessoryInfo.${id}.json`);
|
|
62
108
|
const identifierCache = (0, node_path_1.join)(persistPath, `IdentifierCache.${id}.json`);
|
|
109
|
+
const deviceId = id.includes(':') ? id.split(':').join('').toUpperCase() : id.toUpperCase();
|
|
110
|
+
const matterPath = (0, node_path_1.join)(this.configService.storagePath, 'matter', deviceId);
|
|
63
111
|
try {
|
|
64
112
|
const configFile = await this.configEditorService.getConfigFile();
|
|
65
|
-
const username = id.match(/.{1,2}/g)
|
|
113
|
+
const username = id.includes(':') ? id.toUpperCase() : id.match(/.{1,2}/g)?.join(':').toUpperCase() || id.toUpperCase();
|
|
66
114
|
const uiConfig = configFile.platforms.find(x => x.platform === 'config');
|
|
67
115
|
let blacklistChanged = false;
|
|
68
116
|
if (uiConfig.accessoryControl?.instanceBlacklist?.includes(username)) {
|
|
@@ -112,6 +160,10 @@ let ServerService = class ServerService {
|
|
|
112
160
|
await (0, fs_extra_1.unlink)(identifierCache);
|
|
113
161
|
this.logger.warn(`Bridge ${id} reset: removed ${identifierCache}.`);
|
|
114
162
|
}
|
|
163
|
+
if (await (0, fs_extra_1.pathExists)(matterPath)) {
|
|
164
|
+
await (0, fs_extra_1.remove)(matterPath);
|
|
165
|
+
this.logger.warn(`Bridge ${id} reset: removed Matter bridge storage at ${matterPath}.`);
|
|
166
|
+
}
|
|
115
167
|
await this.deleteDeviceAccessories(id);
|
|
116
168
|
}
|
|
117
169
|
async restartServer() {
|
|
@@ -155,6 +207,12 @@ let ServerService = class ServerService {
|
|
|
155
207
|
await this.configEditorService.updateConfigFile(configFile);
|
|
156
208
|
await (0, fs_extra_1.remove)((0, node_path_1.resolve)(this.configService.storagePath, 'accessories'));
|
|
157
209
|
await (0, fs_extra_1.remove)((0, node_path_1.resolve)(this.configService.storagePath, 'persist'));
|
|
210
|
+
const deviceId = oldUsername.split(':').join('').toUpperCase();
|
|
211
|
+
const matterPath = (0, node_path_1.join)(this.configService.storagePath, 'matter', deviceId);
|
|
212
|
+
if (await (0, fs_extra_1.pathExists)(matterPath)) {
|
|
213
|
+
await (0, fs_extra_1.remove)(matterPath);
|
|
214
|
+
this.logger.warn(`Bridge ${oldUsername} reset: removed Matter bridge storage at ${matterPath}.`);
|
|
215
|
+
}
|
|
158
216
|
this.logger.log('Homebridge bridge reset: accessories and persist directories were removed.');
|
|
159
217
|
}
|
|
160
218
|
async getDevicePairings() {
|
|
@@ -162,9 +220,68 @@ let ServerService = class ServerService {
|
|
|
162
220
|
const devices = (await (0, fs_extra_1.readdir)(persistPath))
|
|
163
221
|
.filter(x => x.match(/AccessoryInfo\.([A-Fa-f0-9]+)\.json$/));
|
|
164
222
|
const configFile = await this.configEditorService.getConfigFile();
|
|
165
|
-
|
|
223
|
+
const hapDevices = await Promise.all(devices.map(async (x) => {
|
|
166
224
|
return await this.getDevicePairingById(x.split('.')[1], configFile);
|
|
167
225
|
}));
|
|
226
|
+
const matterExternalDevices = await this.getMatterExternalAccessories(configFile, hapDevices);
|
|
227
|
+
return [...hapDevices, ...matterExternalDevices].sort((a, b) => a.name.localeCompare(b.name));
|
|
228
|
+
}
|
|
229
|
+
async getMatterExternalAccessories(configFile, hapDevices) {
|
|
230
|
+
const matterPath = (0, node_path_1.join)(this.configService.storagePath, 'matter');
|
|
231
|
+
if (!await (0, fs_extra_1.pathExists)(matterPath)) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
const matterDirs = (await (0, fs_extra_1.readdir)(matterPath))
|
|
235
|
+
.filter(x => x.match(/^[A-F0-9]{12}$/));
|
|
236
|
+
const matterExternalDevices = [];
|
|
237
|
+
for (const deviceId of matterDirs) {
|
|
238
|
+
try {
|
|
239
|
+
const hasHapAccessoryInfo = hapDevices.some(d => d._id === deviceId);
|
|
240
|
+
if (hasHapAccessoryInfo) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const mainBridgeId = this.configService.homebridgeConfig.bridge.username.split(':').join('').toUpperCase();
|
|
244
|
+
if (deviceId.toUpperCase() === mainBridgeId) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const accessoriesPath = (0, node_path_1.join)(matterPath, deviceId, 'accessories.json');
|
|
248
|
+
if (!await (0, fs_extra_1.pathExists)(accessoriesPath)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const accessories = await (0, fs_extra_1.readJson)(accessoriesPath);
|
|
252
|
+
if (!Array.isArray(accessories) || accessories.length === 0) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const accessory = accessories[0];
|
|
256
|
+
const commissioningPath = (0, node_path_1.join)(matterPath, deviceId, 'commissioning.json');
|
|
257
|
+
let commissioned = false;
|
|
258
|
+
if (await (0, fs_extra_1.pathExists)(commissioningPath)) {
|
|
259
|
+
const commissioningInfo = await (0, fs_extra_1.readJson)(commissioningPath);
|
|
260
|
+
commissioned = commissioningInfo.commissioned || false;
|
|
261
|
+
}
|
|
262
|
+
const device = {
|
|
263
|
+
_id: deviceId,
|
|
264
|
+
_username: deviceId.match(/.{1,2}/g)?.join(':').toUpperCase() || deviceId,
|
|
265
|
+
_main: false,
|
|
266
|
+
_category: 'other',
|
|
267
|
+
_matter: true,
|
|
268
|
+
_matterOnly: true,
|
|
269
|
+
_isPaired: commissioned,
|
|
270
|
+
_plugin: accessory.plugin,
|
|
271
|
+
name: accessory.displayName || 'Matter External Accessory',
|
|
272
|
+
displayName: accessory.displayName || 'Matter External Accessory',
|
|
273
|
+
manufacturer: accessory.manufacturer || 'Unknown',
|
|
274
|
+
model: accessory.model || 'Unknown',
|
|
275
|
+
serialNumber: accessory.serialNumber || deviceId,
|
|
276
|
+
category: 1,
|
|
277
|
+
};
|
|
278
|
+
matterExternalDevices.push(device);
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
this.logger.error(`Failed to read Matter external accessory ${deviceId}: ${e.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return matterExternalDevices;
|
|
168
285
|
}
|
|
169
286
|
async getDevicePairingById(deviceId, configFile = null) {
|
|
170
287
|
const persistPath = (0, node_path_1.join)(this.configService.storagePath, 'persist');
|
|
@@ -197,6 +314,10 @@ let ServerService = class ServerService {
|
|
|
197
314
|
device._isPaired = device.pairedClients && Object.keys(device.pairedClients).length > 0;
|
|
198
315
|
device._setupCode = this.generateSetupCode(device);
|
|
199
316
|
device._couldBeStale = !device._main && device._category === 'bridge' && !pluginBlock;
|
|
317
|
+
device._matter = !!(pluginBlock?._bridge?.matter);
|
|
318
|
+
if (device._matter && pluginBlock && 'accessory' in pluginBlock) {
|
|
319
|
+
this.logger.warn(`Device ${deviceId} has Matter configuration on an accessory-based plugin. Matter is only supported for platform-based plugins.`);
|
|
320
|
+
}
|
|
200
321
|
delete device.signSk;
|
|
201
322
|
delete device.signPk;
|
|
202
323
|
delete device.configHash;
|
|
@@ -210,6 +331,44 @@ let ServerService = class ServerService {
|
|
|
210
331
|
await this.deleteSingleDevicePairing(id, resetPairingInfo);
|
|
211
332
|
return { ok: true };
|
|
212
333
|
}
|
|
334
|
+
async deleteDeviceMatterConfig(id) {
|
|
335
|
+
try {
|
|
336
|
+
const configFile = await this.configEditorService.getConfigFile();
|
|
337
|
+
const username = id.includes(':') ? id.toUpperCase() : id.match(/.{1,2}/g)?.join(':').toUpperCase() || id.toUpperCase();
|
|
338
|
+
const pluginBlocks = [
|
|
339
|
+
...(configFile.accessories || []),
|
|
340
|
+
...(configFile.platforms || []),
|
|
341
|
+
]
|
|
342
|
+
.filter((block) => block._bridge?.username?.toUpperCase() === username.toUpperCase());
|
|
343
|
+
const pluginBlock = pluginBlocks.find((block) => block._bridge?.matter);
|
|
344
|
+
if (!pluginBlock) {
|
|
345
|
+
this.logger.error(`Failed to find Matter configuration for child bridge ${id}.`);
|
|
346
|
+
throw new common_1.NotFoundException(`Matter configuration not found for bridge ${id}`);
|
|
347
|
+
}
|
|
348
|
+
if ('accessory' in pluginBlock) {
|
|
349
|
+
this.logger.warn(`Removing Matter configuration from accessory-based plugin block for bridge ${id}. Matter is only supported for platform-based plugins.`);
|
|
350
|
+
}
|
|
351
|
+
delete pluginBlock._bridge.matter;
|
|
352
|
+
this.logger.warn(`Bridge ${id} Matter configuration removed from config.json.`);
|
|
353
|
+
await this.configEditorService.updateConfigFile(configFile);
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
if (e instanceof common_1.NotFoundException) {
|
|
357
|
+
throw e;
|
|
358
|
+
}
|
|
359
|
+
this.logger.error(`Failed to remove Matter configuration for child bridge ${id} as ${e.message}.`);
|
|
360
|
+
throw new common_1.InternalServerErrorException(`Failed to remove Matter configuration: ${e.message}`);
|
|
361
|
+
}
|
|
362
|
+
this.logger.warn(`Shutting down Homebridge before removing Matter storage for bridge ${id}...`);
|
|
363
|
+
await this.homebridgeIpcService.restartAndWaitForClose();
|
|
364
|
+
const deviceId = id.includes(':') ? id.split(':').join('').toUpperCase() : id.toUpperCase();
|
|
365
|
+
const matterPath = (0, node_path_1.join)(this.configService.storagePath, 'matter', deviceId);
|
|
366
|
+
if (await (0, fs_extra_1.pathExists)(matterPath)) {
|
|
367
|
+
await (0, fs_extra_1.remove)(matterPath);
|
|
368
|
+
this.logger.warn(`Bridge ${id} Matter storage removed at ${matterPath}.`);
|
|
369
|
+
}
|
|
370
|
+
return { ok: true };
|
|
371
|
+
}
|
|
213
372
|
async deleteDevicesPairing(bridges) {
|
|
214
373
|
this.logger.warn(`Shutting down Homebridge before resetting paired bridges ${bridges.map(x => x.id).join(', ')}...`);
|
|
215
374
|
await this.homebridgeIpcService.restartAndWaitForClose();
|
|
@@ -233,9 +392,9 @@ let ServerService = class ServerService {
|
|
|
233
392
|
this.logger.warn(`Shutting down Homebridge before removing accessories for paired bridges ${bridges.map(x => x.id).join(', ')}...`);
|
|
234
393
|
await this.homebridgeIpcService.restartAndWaitForClose();
|
|
235
394
|
const cachedAccessoriesDir = (0, node_path_1.join)(this.configService.storagePath, 'accessories');
|
|
236
|
-
for (const { id } of bridges) {
|
|
395
|
+
for (const { id, protocol } of bridges) {
|
|
237
396
|
try {
|
|
238
|
-
await this.deleteSingleDeviceAccessories(id, cachedAccessoriesDir);
|
|
397
|
+
await this.deleteSingleDeviceAccessories(id, cachedAccessoriesDir, protocol || 'both');
|
|
239
398
|
}
|
|
240
399
|
catch (e) {
|
|
241
400
|
this.logger.error(`Failed to remove accessories for bridge ${id} as ${e.message}.`);
|
|
@@ -316,13 +475,19 @@ let ServerService = class ServerService {
|
|
|
316
475
|
await this.homebridgeIpcService.restartAndWaitForClose();
|
|
317
476
|
this.logger.warn('Shutting down Homebridge before removing cached accessories');
|
|
318
477
|
try {
|
|
319
|
-
this.logger.log('Clearing all cached accessories...');
|
|
478
|
+
this.logger.log('Clearing all HAP cached accessories...');
|
|
320
479
|
for (const thisCachedAccessoriesPath of cachedAccessoryPaths) {
|
|
321
480
|
if (await (0, fs_extra_1.pathExists)(thisCachedAccessoriesPath)) {
|
|
322
481
|
await (0, fs_extra_1.unlink)(thisCachedAccessoriesPath);
|
|
323
482
|
this.logger.warn(`Removed ${thisCachedAccessoriesPath}.`);
|
|
324
483
|
}
|
|
325
484
|
}
|
|
485
|
+
const matterDir = (0, node_path_1.join)(this.configService.storagePath, 'matter');
|
|
486
|
+
if (await (0, fs_extra_1.pathExists)(matterDir)) {
|
|
487
|
+
this.logger.log('Clearing all Matter cached accessories...');
|
|
488
|
+
await (0, fs_extra_1.remove)(matterDir);
|
|
489
|
+
this.logger.warn(`Removed Matter storage directory at ${matterDir}.`);
|
|
490
|
+
}
|
|
326
491
|
}
|
|
327
492
|
catch (e) {
|
|
328
493
|
this.logger.error(`Failed to clear all cached accessories at ${cachedAccessoriesPath} as ${e.message}.`);
|
|
@@ -331,6 +496,96 @@ let ServerService = class ServerService {
|
|
|
331
496
|
}
|
|
332
497
|
return { ok: true };
|
|
333
498
|
}
|
|
499
|
+
async getMatterAccessories() {
|
|
500
|
+
const matterDir = (0, node_path_1.join)(this.configService.storagePath, 'matter');
|
|
501
|
+
if (!await (0, fs_extra_1.pathExists)(matterDir)) {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
const matterBridges = (await (0, fs_extra_1.readdir)(matterDir))
|
|
505
|
+
.filter(x => x.match(/^[A-F0-9]+$/));
|
|
506
|
+
const matterAccessories = [];
|
|
507
|
+
await Promise.all(matterBridges.map(async (deviceId) => {
|
|
508
|
+
try {
|
|
509
|
+
const accessoriesPath = (0, node_path_1.join)(matterDir, deviceId, 'accessories.json');
|
|
510
|
+
if (await (0, fs_extra_1.pathExists)(accessoriesPath)) {
|
|
511
|
+
const accessories = await (0, fs_extra_1.readJson)(accessoriesPath);
|
|
512
|
+
if (Array.isArray(accessories)) {
|
|
513
|
+
for (const accessory of accessories) {
|
|
514
|
+
accessory.$deviceId = deviceId;
|
|
515
|
+
accessory.$protocol = 'matter';
|
|
516
|
+
matterAccessories.push(accessory);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
this.logger.error(`Failed to read Matter accessories for bridge ${deviceId}: ${e.message}`);
|
|
523
|
+
}
|
|
524
|
+
}));
|
|
525
|
+
return matterAccessories;
|
|
526
|
+
}
|
|
527
|
+
async deleteMatterAccessory(deviceId, uuid) {
|
|
528
|
+
const matterAccessoriesPath = (0, node_path_1.join)(this.configService.storagePath, 'matter', deviceId, 'accessories.json');
|
|
529
|
+
if (!await (0, fs_extra_1.pathExists)(matterAccessoriesPath)) {
|
|
530
|
+
this.logger.error(`Matter accessories file not found for bridge ${deviceId}`);
|
|
531
|
+
throw new common_1.NotFoundException();
|
|
532
|
+
}
|
|
533
|
+
this.logger.warn(`Shutting down Homebridge before removing Matter accessory ${uuid} from bridge ${deviceId}...`);
|
|
534
|
+
await this.homebridgeIpcService.restartAndWaitForClose();
|
|
535
|
+
const matterAccessories = await (0, fs_extra_1.readJson)(matterAccessoriesPath);
|
|
536
|
+
const accessoryIndex = matterAccessories.findIndex(x => x.uuid === uuid);
|
|
537
|
+
if (accessoryIndex > -1) {
|
|
538
|
+
matterAccessories.splice(accessoryIndex, 1);
|
|
539
|
+
await (0, fs_extra_1.writeJson)(matterAccessoriesPath, matterAccessories, { spaces: 2 });
|
|
540
|
+
this.logger.warn(`Removed Matter accessory with UUID ${uuid} from bridge ${deviceId}.`);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
this.logger.error(`Cannot find Matter accessory with UUID ${uuid} in bridge ${deviceId}.`);
|
|
544
|
+
throw new common_1.NotFoundException();
|
|
545
|
+
}
|
|
546
|
+
return { ok: true };
|
|
547
|
+
}
|
|
548
|
+
async deleteMatterAccessories(accessories) {
|
|
549
|
+
this.logger.warn(`Shutting down Homebridge before removing Matter accessories ${accessories.map(x => x.uuid).join(', ')}.`);
|
|
550
|
+
await this.homebridgeIpcService.restartAndWaitForClose();
|
|
551
|
+
const accessoriesByBridge = new Map();
|
|
552
|
+
for (const { deviceId, uuid } of accessories) {
|
|
553
|
+
if (!accessoriesByBridge.has(deviceId)) {
|
|
554
|
+
accessoriesByBridge.set(deviceId, []);
|
|
555
|
+
}
|
|
556
|
+
accessoriesByBridge.get(deviceId).push({ uuid });
|
|
557
|
+
}
|
|
558
|
+
for (const [deviceId, bridgeAccessories] of accessoriesByBridge.entries()) {
|
|
559
|
+
const matterAccessoriesPath = (0, node_path_1.join)(this.configService.storagePath, 'matter', deviceId, 'accessories.json');
|
|
560
|
+
try {
|
|
561
|
+
if (!await (0, fs_extra_1.pathExists)(matterAccessoriesPath)) {
|
|
562
|
+
this.logger.error(`Matter accessories file not found for bridge ${deviceId}`);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const matterAccessories = await (0, fs_extra_1.readJson)(matterAccessoriesPath);
|
|
566
|
+
for (const { uuid } of bridgeAccessories) {
|
|
567
|
+
try {
|
|
568
|
+
const accessoryIndex = matterAccessories.findIndex(x => x.uuid === uuid);
|
|
569
|
+
if (accessoryIndex > -1) {
|
|
570
|
+
matterAccessories.splice(accessoryIndex, 1);
|
|
571
|
+
this.logger.warn(`Removed Matter accessory with UUID ${uuid} from bridge ${deviceId}.`);
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
this.logger.error(`Cannot find Matter accessory with UUID ${uuid} in bridge ${deviceId}.`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (e) {
|
|
578
|
+
this.logger.error(`Failed to remove Matter accessory with UUID ${uuid} from bridge ${deviceId} as ${e.message}.`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
await (0, fs_extra_1.writeJson)(matterAccessoriesPath, matterAccessories, { spaces: 2 });
|
|
582
|
+
}
|
|
583
|
+
catch (e) {
|
|
584
|
+
this.logger.error(`Failed to process Matter accessories for bridge ${deviceId} as ${e.message}.`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return { ok: true };
|
|
588
|
+
}
|
|
334
589
|
async getSetupCode() {
|
|
335
590
|
if (this.setupCode) {
|
|
336
591
|
return this.setupCode;
|
|
@@ -435,6 +690,30 @@ let ServerService = class ServerService {
|
|
|
435
690
|
}
|
|
436
691
|
return { port };
|
|
437
692
|
}
|
|
693
|
+
async lookupUnusedMatterPort() {
|
|
694
|
+
const min = 5530;
|
|
695
|
+
const max = 5541;
|
|
696
|
+
const config = await this.configEditorService.getConfigFile();
|
|
697
|
+
const usedMatterPorts = new Set();
|
|
698
|
+
if (config.bridge?.matter?.port) {
|
|
699
|
+
usedMatterPorts.add(config.bridge.matter.port);
|
|
700
|
+
}
|
|
701
|
+
for (const block of [...(config.accessories || []), ...(config.platforms || [])]) {
|
|
702
|
+
if (block._bridge?.matter?.port) {
|
|
703
|
+
if ('accessory' in block) {
|
|
704
|
+
this.logger.warn(`Found Matter configuration on accessory-based plugin block, skipping port ${block._bridge.matter.port}`);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
usedMatterPorts.add(block._bridge.matter.port);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
for (let port = min; port <= max; port += 1) {
|
|
711
|
+
if (!usedMatterPorts.has(port) && !await (0, tcp_port_used_1.check)(port)) {
|
|
712
|
+
return { port };
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
throw new common_1.InternalServerErrorException('No available ports in the Matter port range (5530-5541)');
|
|
716
|
+
}
|
|
438
717
|
async getHomebridgePort() {
|
|
439
718
|
const config = await this.configEditorService.getConfigFile();
|
|
440
719
|
return { port: config.bridge.port };
|
|
@@ -555,7 +834,7 @@ let ServerService = class ServerService {
|
|
|
555
834
|
async nodeVersionChanged() {
|
|
556
835
|
return new Promise((res) => {
|
|
557
836
|
let result = false;
|
|
558
|
-
const child = (0, node_child_process_1.spawn)(node_process_1.default.execPath, ['-v']
|
|
837
|
+
const child = (0, node_child_process_1.spawn)(node_process_1.default.execPath, ['-v']);
|
|
559
838
|
child.stdout.once('data', (data) => {
|
|
560
839
|
result = data.toString().trim() !== node_process_1.default.version;
|
|
561
840
|
});
|
|
@@ -567,6 +846,248 @@ let ServerService = class ServerService {
|
|
|
567
846
|
});
|
|
568
847
|
});
|
|
569
848
|
}
|
|
849
|
+
async uploadSslKeyCert(req) {
|
|
850
|
+
const parts = req.parts ? req.parts() : null;
|
|
851
|
+
const files = [];
|
|
852
|
+
if (parts) {
|
|
853
|
+
for await (const part of parts) {
|
|
854
|
+
if (part.file) {
|
|
855
|
+
files.push(part);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
const single = await req.file();
|
|
861
|
+
if (single?.file) {
|
|
862
|
+
files.push(single);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (!files.length) {
|
|
866
|
+
throw new common_1.BadRequestException('No files uploaded. Please upload both the private key and certificate files.');
|
|
867
|
+
}
|
|
868
|
+
const readStreamToBuffer = async (stream) => {
|
|
869
|
+
const chunks = [];
|
|
870
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
871
|
+
stream.on('data', (d) => chunks.push(node_buffer_1.Buffer.isBuffer(d) ? d : node_buffer_1.Buffer.from(d)));
|
|
872
|
+
stream.on('end', () => resolvePromise());
|
|
873
|
+
stream.on('error', rejectPromise);
|
|
874
|
+
});
|
|
875
|
+
return node_buffer_1.Buffer.concat(chunks);
|
|
876
|
+
};
|
|
877
|
+
let keyPem = null;
|
|
878
|
+
let certPem = null;
|
|
879
|
+
for (const f of files) {
|
|
880
|
+
if (f.file?.truncated) {
|
|
881
|
+
throw new common_1.InternalServerErrorException(`Upload exceeds maximum size ${globalThis.backup.maxBackupSizeText}.`);
|
|
882
|
+
}
|
|
883
|
+
const buf = await readStreamToBuffer(f.file);
|
|
884
|
+
const text = buf.toString('utf8');
|
|
885
|
+
if (/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/.test(text)) {
|
|
886
|
+
keyPem = buf;
|
|
887
|
+
}
|
|
888
|
+
else if (/-----BEGIN CERTIFICATE-----/.test(text)) {
|
|
889
|
+
certPem = buf;
|
|
890
|
+
}
|
|
891
|
+
else if (f.fieldname === 'key') {
|
|
892
|
+
keyPem = buf;
|
|
893
|
+
}
|
|
894
|
+
else if (f.fieldname === 'cert') {
|
|
895
|
+
certPem = buf;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (!keyPem || !certPem) {
|
|
899
|
+
throw new common_1.BadRequestException('Both a PEM private key and certificate must be provided.');
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
const x509 = new node_crypto_1.X509Certificate(certPem);
|
|
903
|
+
const certPub = x509.publicKey.export({ type: 'spki', format: 'der' });
|
|
904
|
+
const priv = (0, node_crypto_1.createPrivateKey)({ key: keyPem });
|
|
905
|
+
const pubFromPriv = (0, node_crypto_1.createPublicKey)(priv).export({ type: 'spki', format: 'der' });
|
|
906
|
+
if (!certPub.equals(pubFromPriv)) {
|
|
907
|
+
throw new common_1.BadRequestException('The private key does not match the certificate public key.');
|
|
908
|
+
}
|
|
909
|
+
(0, node_tls_1.createSecureContext)({ key: keyPem, cert: certPem });
|
|
910
|
+
}
|
|
911
|
+
catch (e) {
|
|
912
|
+
if (e instanceof common_1.BadRequestException) {
|
|
913
|
+
throw e;
|
|
914
|
+
}
|
|
915
|
+
throw new common_1.BadRequestException(`Invalid key/certificate: ${e?.message || e}`);
|
|
916
|
+
}
|
|
917
|
+
const sslDir = (0, node_path_1.join)(this.configService.storagePath, 'ssl-certs');
|
|
918
|
+
const keyPath = (0, node_path_1.join)(sslDir, 'ui-ssl.key');
|
|
919
|
+
const certPath = (0, node_path_1.join)(sslDir, 'ui-ssl.crt');
|
|
920
|
+
const { ensureDir, writeFile } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
|
|
921
|
+
await ensureDir(sslDir);
|
|
922
|
+
await writeFile(keyPath, keyPem);
|
|
923
|
+
await writeFile(certPath, certPem);
|
|
924
|
+
const configFile = await this.configEditorService.getConfigFile();
|
|
925
|
+
const uiConfigBlock = configFile.platforms.find((x) => x.platform === 'config');
|
|
926
|
+
if (!uiConfigBlock) {
|
|
927
|
+
throw new common_1.InternalServerErrorException('Config platform block not found.');
|
|
928
|
+
}
|
|
929
|
+
if (!uiConfigBlock.ssl) {
|
|
930
|
+
uiConfigBlock.ssl = {};
|
|
931
|
+
}
|
|
932
|
+
uiConfigBlock.ssl.key = keyPath;
|
|
933
|
+
uiConfigBlock.ssl.cert = certPath;
|
|
934
|
+
delete uiConfigBlock.ssl.pfx;
|
|
935
|
+
delete uiConfigBlock.ssl.passphrase;
|
|
936
|
+
uiConfigBlock.ssl.selfSigned = false;
|
|
937
|
+
await this.configEditorService.updateConfigFile(configFile);
|
|
938
|
+
return {
|
|
939
|
+
ok: true,
|
|
940
|
+
type: 'keycert',
|
|
941
|
+
keyPath,
|
|
942
|
+
certPath,
|
|
943
|
+
details: 'Certificate and key validated and saved.',
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
async uploadSslPfx(req) {
|
|
947
|
+
let passphrase;
|
|
948
|
+
let filePart;
|
|
949
|
+
if (req.parts) {
|
|
950
|
+
for await (const part of req.parts()) {
|
|
951
|
+
if (part.type === 'file' || part.file) {
|
|
952
|
+
filePart = part;
|
|
953
|
+
}
|
|
954
|
+
else if (part.type === 'field' || part.value) {
|
|
955
|
+
if (part.fieldname === 'passphrase') {
|
|
956
|
+
passphrase = part.value;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
filePart = await req.file();
|
|
963
|
+
passphrase = req.body?.passphrase;
|
|
964
|
+
}
|
|
965
|
+
if (!filePart) {
|
|
966
|
+
throw new common_1.BadRequestException('No PFX file uploaded.');
|
|
967
|
+
}
|
|
968
|
+
if (filePart.file?.truncated) {
|
|
969
|
+
throw new common_1.InternalServerErrorException(`Upload exceeds maximum size ${globalThis.backup.maxBackupSizeText}.`);
|
|
970
|
+
}
|
|
971
|
+
const readStreamToBuffer = async (stream) => {
|
|
972
|
+
const chunks = [];
|
|
973
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
974
|
+
stream.on('data', (d) => chunks.push(node_buffer_1.Buffer.isBuffer(d) ? d : node_buffer_1.Buffer.from(d)));
|
|
975
|
+
stream.on('end', () => resolvePromise());
|
|
976
|
+
stream.on('error', rejectPromise);
|
|
977
|
+
});
|
|
978
|
+
return node_buffer_1.Buffer.concat(chunks);
|
|
979
|
+
};
|
|
980
|
+
const pfxBuffer = await readStreamToBuffer(filePart.file);
|
|
981
|
+
try {
|
|
982
|
+
(0, node_tls_1.createSecureContext)({ pfx: pfxBuffer, passphrase });
|
|
983
|
+
}
|
|
984
|
+
catch (e) {
|
|
985
|
+
throw new common_1.BadRequestException(`Invalid PFX or passphrase: ${e?.message || e}`);
|
|
986
|
+
}
|
|
987
|
+
const sslDir = (0, node_path_1.join)(this.configService.storagePath, 'ssl-certs');
|
|
988
|
+
const pfxPath = (0, node_path_1.join)(sslDir, 'ui-ssl.pfx');
|
|
989
|
+
const { ensureDir, writeFile } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
|
|
990
|
+
await ensureDir(sslDir);
|
|
991
|
+
await writeFile(pfxPath, pfxBuffer);
|
|
992
|
+
const configFile = await this.configEditorService.getConfigFile();
|
|
993
|
+
const uiConfigBlock = configFile.platforms.find((x) => x.platform === 'config');
|
|
994
|
+
if (!uiConfigBlock) {
|
|
995
|
+
throw new common_1.InternalServerErrorException('Config platform block not found.');
|
|
996
|
+
}
|
|
997
|
+
if (!uiConfigBlock.ssl) {
|
|
998
|
+
uiConfigBlock.ssl = {};
|
|
999
|
+
}
|
|
1000
|
+
uiConfigBlock.ssl.pfx = pfxPath;
|
|
1001
|
+
uiConfigBlock.ssl.passphrase = passphrase || '';
|
|
1002
|
+
delete uiConfigBlock.ssl.key;
|
|
1003
|
+
delete uiConfigBlock.ssl.cert;
|
|
1004
|
+
uiConfigBlock.ssl.selfSigned = false;
|
|
1005
|
+
await this.configEditorService.updateConfigFile(configFile);
|
|
1006
|
+
return {
|
|
1007
|
+
ok: true,
|
|
1008
|
+
type: 'pfx',
|
|
1009
|
+
pfxPath,
|
|
1010
|
+
details: 'PFX validated and saved.',
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
async validateCurrentSslConfig() {
|
|
1014
|
+
const configFile = await this.configEditorService.getConfigFile();
|
|
1015
|
+
const uiConfigBlock = configFile.platforms.find((x) => x.platform === 'config');
|
|
1016
|
+
const ssl = uiConfigBlock?.ssl || {};
|
|
1017
|
+
if (!ssl || (!ssl.selfSigned && !ssl.key && !ssl.cert && !ssl.pfx)) {
|
|
1018
|
+
return { ok: true, valid: true, type: 'off', details: 'HTTPS is disabled.' };
|
|
1019
|
+
}
|
|
1020
|
+
if (ssl.selfSigned) {
|
|
1021
|
+
return { ok: true, valid: true, type: 'selfsigned', details: 'Self-signed mode enabled.' };
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
if (ssl.key && ssl.cert) {
|
|
1025
|
+
const { readFile } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
|
|
1026
|
+
const keyPem = await readFile(ssl.key);
|
|
1027
|
+
const certPem = await readFile(ssl.cert);
|
|
1028
|
+
const x509 = new node_crypto_1.X509Certificate(certPem);
|
|
1029
|
+
const certPub = x509.publicKey.export({ type: 'spki', format: 'der' });
|
|
1030
|
+
const priv = (0, node_crypto_1.createPrivateKey)({ key: keyPem });
|
|
1031
|
+
const pubFromPriv = (0, node_crypto_1.createPublicKey)(priv).export({ type: 'spki', format: 'der' });
|
|
1032
|
+
if (!certPub.equals(pubFromPriv)) {
|
|
1033
|
+
return { ok: true, valid: false, type: 'keycert', details: 'Private key does not match certificate.' };
|
|
1034
|
+
}
|
|
1035
|
+
(0, node_tls_1.createSecureContext)({ key: keyPem, cert: certPem });
|
|
1036
|
+
return { ok: true, valid: true, type: 'keycert', details: 'Key and certificate are valid and match.' };
|
|
1037
|
+
}
|
|
1038
|
+
if (ssl.pfx) {
|
|
1039
|
+
const { readFile } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
|
|
1040
|
+
const pfx = await readFile(ssl.pfx);
|
|
1041
|
+
(0, node_tls_1.createSecureContext)({ pfx, passphrase: ssl.passphrase });
|
|
1042
|
+
return { ok: true, valid: true, type: 'pfx', details: 'PFX file and passphrase are valid.' };
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
catch (e) {
|
|
1046
|
+
return { ok: true, valid: false, type: ssl.pfx ? 'pfx' : 'keycert', details: e?.message || String(e) };
|
|
1047
|
+
}
|
|
1048
|
+
return { ok: true, valid: false, type: 'off', details: 'No SSL configuration found.' };
|
|
1049
|
+
}
|
|
1050
|
+
async generateSelfSignedCertificate(options = {}) {
|
|
1051
|
+
const hostnames = Array.isArray(options.hostnames) && options.hostnames.length
|
|
1052
|
+
? options.hostnames.map(h => String(h).trim()).filter(Boolean)
|
|
1053
|
+
: ['localhost', '127.0.0.1'];
|
|
1054
|
+
const mode = options.mode || 'keycert';
|
|
1055
|
+
const generator = new ssl_cert_generator_service_1.SslCertGeneratorService();
|
|
1056
|
+
await generator.generateCertificate(hostnames);
|
|
1057
|
+
const sslDir = (0, node_path_1.join)(this.configService.storagePath, 'ssl-certs');
|
|
1058
|
+
const keyPath = (0, node_path_1.join)(sslDir, 'private-key.pem');
|
|
1059
|
+
const certPath = (0, node_path_1.join)(sslDir, 'certificate.pem');
|
|
1060
|
+
const configFile = await this.configEditorService.getConfigFile();
|
|
1061
|
+
const uiConfigBlock = configFile.platforms.find((x) => x.platform === 'config');
|
|
1062
|
+
if (!uiConfigBlock.ssl) {
|
|
1063
|
+
uiConfigBlock.ssl = {};
|
|
1064
|
+
}
|
|
1065
|
+
if (mode === 'keycert') {
|
|
1066
|
+
uiConfigBlock.ssl.key = keyPath;
|
|
1067
|
+
uiConfigBlock.ssl.cert = certPath;
|
|
1068
|
+
delete uiConfigBlock.ssl.pfx;
|
|
1069
|
+
delete uiConfigBlock.ssl.passphrase;
|
|
1070
|
+
uiConfigBlock.ssl.selfSigned = false;
|
|
1071
|
+
uiConfigBlock.ssl.selfSignedHostnames = hostnames;
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
delete uiConfigBlock.ssl.key;
|
|
1075
|
+
delete uiConfigBlock.ssl.cert;
|
|
1076
|
+
delete uiConfigBlock.ssl.pfx;
|
|
1077
|
+
delete uiConfigBlock.ssl.passphrase;
|
|
1078
|
+
uiConfigBlock.ssl.selfSigned = true;
|
|
1079
|
+
uiConfigBlock.ssl.selfSignedHostnames = hostnames;
|
|
1080
|
+
}
|
|
1081
|
+
await this.configEditorService.updateConfigFile(configFile);
|
|
1082
|
+
return {
|
|
1083
|
+
ok: true,
|
|
1084
|
+
type: 'generated',
|
|
1085
|
+
mode,
|
|
1086
|
+
keyPath: mode === 'keycert' ? keyPath : undefined,
|
|
1087
|
+
certPath: mode === 'keycert' ? certPath : undefined,
|
|
1088
|
+
details: `Self-signed certificate generated for ${hostnames.join(', ')}`,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
570
1091
|
};
|
|
571
1092
|
exports.ServerService = ServerService;
|
|
572
1093
|
exports.ServerService = ServerService = __decorate([
|