@camstack/addon-provider-bosch 0.1.29 → 0.1.30
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 +6 -0
- package/dist/{account-integration-offline-DMtMWixt.js → account-integration-offline-BMoYdiX2.js} +126 -1
- package/dist/{account-integration-offline-C-XbrgLs.mjs → account-integration-offline-pv6cqOKu.mjs} +115 -2
- package/dist/{dist-CyLs51d2.js → dist-BioIJ3NA.js} +1139 -82
- package/dist/{dist-DzSesBKb.mjs → dist-DBUnIovD.mjs} +1128 -83
- package/dist/home-connect/addon.js +349 -259
- package/dist/home-connect/addon.mjs +349 -259
- package/dist/homecom/addon.js +94 -12
- package/dist/homecom/addon.mjs +94 -12
- package/dist/shc/addon.js +1 -1
- package/dist/shc/addon.mjs +1 -1
- package/i18n/en.json +14 -1
- package/i18n/it.json +14 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../dist-
|
|
3
|
-
const require_account_integration_offline = require("../account-integration-offline-
|
|
2
|
+
const require_dist = require("../dist-BioIJ3NA.js");
|
|
3
|
+
const require_account_integration_offline = require("../account-integration-offline-BMoYdiX2.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_tls = require("node:tls");
|
|
6
6
|
let node_zlib = require("node:zlib");
|
|
@@ -31,6 +31,23 @@ var homeConnectActions = require_dist.defineCustomActions({
|
|
|
31
31
|
kind: "mutation",
|
|
32
32
|
auth: "admin"
|
|
33
33
|
}),
|
|
34
|
+
/**
|
|
35
|
+
* The form asks while the proxied login page is open: `pending` until the hub captured the
|
|
36
|
+
* app-scheme redirect and finished the exchange, then the same fields `finishSignIn` returns.
|
|
37
|
+
* Claimed once. A failed exchange answers with its operator-facing message as an error.
|
|
38
|
+
*/
|
|
39
|
+
claimSignIn: require_dist.customAction(require_dist.object({ attemptId: require_dist.string().min(1) }), require_dist.object({
|
|
40
|
+
pending: require_dist.boolean(),
|
|
41
|
+
token: require_dist.string().optional().meta({ secret: true }),
|
|
42
|
+
refresh: require_dist.string().optional().meta({ secret: true }),
|
|
43
|
+
accountLabel: require_dist.string().optional(),
|
|
44
|
+
appliances: require_dist.string().optional(),
|
|
45
|
+
applianceSecrets: require_dist.string().optional().meta({ secret: true }),
|
|
46
|
+
summary: require_dist.string().optional()
|
|
47
|
+
}), {
|
|
48
|
+
kind: "mutation",
|
|
49
|
+
auth: "admin"
|
|
50
|
+
}),
|
|
34
51
|
signOut: require_dist.customAction(require_dist.object({}), require_dist.object({
|
|
35
52
|
signedOut: require_dist.literal(true),
|
|
36
53
|
summary: require_dist.string()
|
|
@@ -987,192 +1004,6 @@ async function finishHomeConnectSignIn(input, store, fetchImpl = homeConnectLogi
|
|
|
987
1004
|
}, fetchImpl);
|
|
988
1005
|
}
|
|
989
1006
|
var homeConnectSignInAttempts = require_account_integration_offline.createSignInAttemptStore();
|
|
990
|
-
//#endregion
|
|
991
|
-
//#region src/home-connect/home-connect-discovery.ts
|
|
992
|
-
/** Dishwasher accessory label: `Dishwasher` plus the appliance name. */
|
|
993
|
-
function homeConnectDishwasherEntityName(containerName) {
|
|
994
|
-
const base = containerName.trim();
|
|
995
|
-
return base.length > 0 ? `Dishwasher ${base}` : "Dishwasher";
|
|
996
|
-
}
|
|
997
|
-
function deviceTypeForApplianceType(type) {
|
|
998
|
-
if (type.toLowerCase().includes("dishwasher")) return require_dist.DeviceType.Container;
|
|
999
|
-
return null;
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* The container candidate for one appliance. Its sensor children are not listed here:
|
|
1003
|
-
* they are the entities the appliance's own description declares, known once it is
|
|
1004
|
-
* connected, and they are attached to the container as that description is read.
|
|
1005
|
-
*/
|
|
1006
|
-
function dishwasherContainerCandidate(app, adoptedId) {
|
|
1007
|
-
const name = app.name.length > 0 ? app.name : app.haId;
|
|
1008
|
-
const children = [{
|
|
1009
|
-
childNativeId: `${app.haId}#dishwasher`,
|
|
1010
|
-
name: homeConnectDishwasherEntityName(name),
|
|
1011
|
-
type: require_dist.DeviceType.Dishwasher,
|
|
1012
|
-
status: "online",
|
|
1013
|
-
metadata: {
|
|
1014
|
-
serialNumber: app.haId,
|
|
1015
|
-
model: app.type
|
|
1016
|
-
},
|
|
1017
|
-
alreadyAdopted: false,
|
|
1018
|
-
adoptedDeviceId: null,
|
|
1019
|
-
capabilities: ["dishwasher"]
|
|
1020
|
-
}];
|
|
1021
|
-
return {
|
|
1022
|
-
childNativeId: app.haId,
|
|
1023
|
-
name,
|
|
1024
|
-
type: require_dist.DeviceType.Container,
|
|
1025
|
-
status: "online",
|
|
1026
|
-
metadata: {
|
|
1027
|
-
serialNumber: app.haId,
|
|
1028
|
-
model: app.type
|
|
1029
|
-
},
|
|
1030
|
-
alreadyAdopted: adoptedId !== null,
|
|
1031
|
-
adoptedDeviceId: adoptedId,
|
|
1032
|
-
capabilities: [],
|
|
1033
|
-
children
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
function buildHomeConnectCandidates(input) {
|
|
1037
|
-
const out = [];
|
|
1038
|
-
for (const app of input.appliances) {
|
|
1039
|
-
if (deviceTypeForApplianceType(app.type) === null) continue;
|
|
1040
|
-
const adoptedId = input.adopted.get(app.haId) ?? null;
|
|
1041
|
-
out.push(dishwasherContainerCandidate(app, adoptedId));
|
|
1042
|
-
}
|
|
1043
|
-
return out;
|
|
1044
|
-
}
|
|
1045
|
-
//#endregion
|
|
1046
|
-
//#region src/home-connect/home-connect-adoption-provider.ts
|
|
1047
|
-
var DEVICES_FILTER = {
|
|
1048
|
-
id: "devices",
|
|
1049
|
-
label: "Devices",
|
|
1050
|
-
isDefault: true
|
|
1051
|
-
};
|
|
1052
|
-
async function adoptedMapForIntegration(integrationId, listAdopted) {
|
|
1053
|
-
const all = await listAdopted();
|
|
1054
|
-
const map = /* @__PURE__ */ new Map();
|
|
1055
|
-
for (const device of all) if (device.config["system"] === "home-connect" && device.config["brokerId"] === integrationId) {
|
|
1056
|
-
const haId = device.config["haId"];
|
|
1057
|
-
if (typeof haId === "string") map.set(haId, device.id);
|
|
1058
|
-
}
|
|
1059
|
-
return map;
|
|
1060
|
-
}
|
|
1061
|
-
function buildHomeConnectAdoptionProvider(deps) {
|
|
1062
|
-
const { registry, hasIntegration, listIntegrations, listAdopted, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
|
|
1063
|
-
async function allCandidatesForIntegration(integrationId) {
|
|
1064
|
-
return buildHomeConnectCandidates({
|
|
1065
|
-
appliances: registry.appliances(integrationId),
|
|
1066
|
-
adopted: await adoptedMapForIntegration(integrationId, listAdopted)
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
return {
|
|
1070
|
-
listCandidateFilters: async () => ({ filters: [DEVICES_FILTER] }),
|
|
1071
|
-
listCandidates: async ({ integrationId, page, pageSize }) => {
|
|
1072
|
-
const cands = await allCandidatesForIntegration(integrationId);
|
|
1073
|
-
const start = (page - 1) * pageSize;
|
|
1074
|
-
return {
|
|
1075
|
-
candidates: cands.slice(start, start + pageSize),
|
|
1076
|
-
totalCount: cands.length,
|
|
1077
|
-
page,
|
|
1078
|
-
pageSize
|
|
1079
|
-
};
|
|
1080
|
-
},
|
|
1081
|
-
getCandidate: async ({ integrationId, childNativeId }) => {
|
|
1082
|
-
return (await allCandidatesForIntegration(integrationId)).find((c) => c.childNativeId === childNativeId) ?? null;
|
|
1083
|
-
},
|
|
1084
|
-
getStatus: async () => {
|
|
1085
|
-
try {
|
|
1086
|
-
let candidateCount = 0;
|
|
1087
|
-
for (const integrationId of listIntegrations()) candidateCount += (await allCandidatesForIntegration(integrationId)).length;
|
|
1088
|
-
const adoptedCount = (await listAdopted()).filter((d) => d.config["system"] === "home-connect").length;
|
|
1089
|
-
return {
|
|
1090
|
-
lastDiscoveryAt: Date.now(),
|
|
1091
|
-
candidateCount,
|
|
1092
|
-
adoptedCount,
|
|
1093
|
-
lastError: null
|
|
1094
|
-
};
|
|
1095
|
-
} catch (err) {
|
|
1096
|
-
return {
|
|
1097
|
-
lastDiscoveryAt: null,
|
|
1098
|
-
candidateCount: 0,
|
|
1099
|
-
adoptedCount: 0,
|
|
1100
|
-
lastError: require_dist.errMsg(err)
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
},
|
|
1104
|
-
refresh: async ({ integrationId }) => {
|
|
1105
|
-
const candidateCount = (await allCandidatesForIntegration(integrationId)).length;
|
|
1106
|
-
const adoptedCount = (await adoptedMapForIntegration(integrationId, listAdopted)).size;
|
|
1107
|
-
return {
|
|
1108
|
-
lastDiscoveryAt: Date.now(),
|
|
1109
|
-
candidateCount,
|
|
1110
|
-
adoptedCount,
|
|
1111
|
-
lastError: null
|
|
1112
|
-
};
|
|
1113
|
-
},
|
|
1114
|
-
adopt: async ({ integrationId, childNativeIds, perCandidate }) => {
|
|
1115
|
-
if (!registry.has(integrationId)) throw new Error(`home-connect adopt: integration ${integrationId} not connected`);
|
|
1116
|
-
const appliances = registry.appliances(integrationId);
|
|
1117
|
-
const adopted = [];
|
|
1118
|
-
let failures = 0;
|
|
1119
|
-
for (const haId of childNativeIds) try {
|
|
1120
|
-
const app = appliances.find((a) => a.haId === haId);
|
|
1121
|
-
if (!app) {
|
|
1122
|
-
failures++;
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
const candidate = buildHomeConnectCandidates({
|
|
1126
|
-
appliances: [app],
|
|
1127
|
-
adopted: /* @__PURE__ */ new Map()
|
|
1128
|
-
})[0];
|
|
1129
|
-
if (!candidate) {
|
|
1130
|
-
failures++;
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
const name = perCandidate?.[haId]?.name ?? app.name;
|
|
1134
|
-
const { deviceId } = await adoptDevice({
|
|
1135
|
-
haId,
|
|
1136
|
-
integrationId,
|
|
1137
|
-
type: candidate.type,
|
|
1138
|
-
name,
|
|
1139
|
-
applianceType: app.type
|
|
1140
|
-
});
|
|
1141
|
-
adopted.push({
|
|
1142
|
-
childNativeId: haId,
|
|
1143
|
-
parentDeviceId: deviceId
|
|
1144
|
-
});
|
|
1145
|
-
} catch (err) {
|
|
1146
|
-
logger.warn("home-connect adopt failed", { meta: {
|
|
1147
|
-
haId,
|
|
1148
|
-
integrationId,
|
|
1149
|
-
error: require_dist.errMsg(err)
|
|
1150
|
-
} });
|
|
1151
|
-
failures++;
|
|
1152
|
-
}
|
|
1153
|
-
if (adopted.length === 0 && failures > 0) throw new Error(`home-connect adopt: all ${failures} adopt(s) failed`);
|
|
1154
|
-
return { adopted: adopted.map((a) => ({
|
|
1155
|
-
...a,
|
|
1156
|
-
accessoryDeviceIds: []
|
|
1157
|
-
})) };
|
|
1158
|
-
},
|
|
1159
|
-
release: async ({ camDeviceId }) => {
|
|
1160
|
-
await removeDevice(camDeviceId);
|
|
1161
|
-
},
|
|
1162
|
-
resync: async ({ camDeviceId }) => {
|
|
1163
|
-
const cfg = await findDeviceConfig(camDeviceId);
|
|
1164
|
-
if (!cfg || cfg["system"] !== "home-connect") throw new Error(`home-connect resync: device ${camDeviceId} not found`);
|
|
1165
|
-
const integrationId = String(cfg["brokerId"]);
|
|
1166
|
-
const haId = String(cfg["haId"]);
|
|
1167
|
-
if (!hasIntegration(integrationId)) throw new Error(`home-connect resync: integration ${integrationId} not connected`);
|
|
1168
|
-
if (!registry.appliances(integrationId).some((a) => a.haId === haId)) throw new Error(`home-connect resync: appliance ${haId} no longer present`);
|
|
1169
|
-
return {
|
|
1170
|
-
changed: false,
|
|
1171
|
-
rebuiltChildren: 0
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
1007
|
var HomeConnectModeSchema = require_dist._enum(["AES", "TLS"]);
|
|
1177
1008
|
function isPlainObject$1(value) {
|
|
1178
1009
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1440,7 +1271,7 @@ function buildHomeConnectFormSchema() {
|
|
|
1440
1271
|
id: "gateway",
|
|
1441
1272
|
title: "Home Connect",
|
|
1442
1273
|
titleKey: "provider-bosch:home-connect.s_gateway.title",
|
|
1443
|
-
description: "Sign in with your Home Connect account.
|
|
1274
|
+
description: "Sign in with your Home Connect account. A new tab opens the sign-in page through this hub and finishes on its own.",
|
|
1444
1275
|
descriptionKey: "provider-bosch:home-connect.s_gateway.description",
|
|
1445
1276
|
columns: 1,
|
|
1446
1277
|
fields: [
|
|
@@ -1453,6 +1284,7 @@ function buildHomeConnectFormSchema() {
|
|
|
1453
1284
|
providerLabel: "Home Connect",
|
|
1454
1285
|
beginAction: "beginSignIn",
|
|
1455
1286
|
completeAction: "finishSignIn",
|
|
1287
|
+
claimAction: "claimSignIn",
|
|
1456
1288
|
signOutAction: "signOut",
|
|
1457
1289
|
accountKey: "accountLabel",
|
|
1458
1290
|
countFromKey: "appliances",
|
|
@@ -1537,6 +1369,295 @@ function mergeAppliancesForPersist(storedAppliances, fromConnection) {
|
|
|
1537
1369
|
return out;
|
|
1538
1370
|
}
|
|
1539
1371
|
//#endregion
|
|
1372
|
+
//#region src/home-connect/home-connect-stored-appliance.ts
|
|
1373
|
+
function stubDeviceDescriptionObject(row) {
|
|
1374
|
+
return { info: {
|
|
1375
|
+
deviceID: row.haId,
|
|
1376
|
+
deviceType: row.type,
|
|
1377
|
+
model: row.name
|
|
1378
|
+
} };
|
|
1379
|
+
}
|
|
1380
|
+
function stubDeviceDescription(row) {
|
|
1381
|
+
return JSON.stringify(stubDeviceDescriptionObject(row));
|
|
1382
|
+
}
|
|
1383
|
+
function serializedDeviceDescription(row) {
|
|
1384
|
+
if (row.deviceDescription === void 0) return stubDeviceDescription(row);
|
|
1385
|
+
if (typeof row.deviceDescription === "string") return row.deviceDescription;
|
|
1386
|
+
return JSON.stringify(row.deviceDescription);
|
|
1387
|
+
}
|
|
1388
|
+
function isDescriptionRecord(value) {
|
|
1389
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1390
|
+
}
|
|
1391
|
+
function parseLoginDeviceDescription(raw) {
|
|
1392
|
+
const trimmed = raw.trim();
|
|
1393
|
+
if (trimmed.length === 0) return {};
|
|
1394
|
+
try {
|
|
1395
|
+
const parsed = JSON.parse(trimmed);
|
|
1396
|
+
if (isDescriptionRecord(parsed)) return { deviceDescription: parsed };
|
|
1397
|
+
} catch {
|
|
1398
|
+
return {};
|
|
1399
|
+
}
|
|
1400
|
+
return {};
|
|
1401
|
+
}
|
|
1402
|
+
function listedApplianceFromStored(row) {
|
|
1403
|
+
return {
|
|
1404
|
+
haId: row.haId,
|
|
1405
|
+
name: row.name,
|
|
1406
|
+
type: row.type,
|
|
1407
|
+
connection: {
|
|
1408
|
+
host: row.host,
|
|
1409
|
+
psk: row.psk,
|
|
1410
|
+
mode: row.mode,
|
|
1411
|
+
iv64: row.iv64 ?? "",
|
|
1412
|
+
deviceDescription: serializedDeviceDescription(row)
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
//#endregion
|
|
1417
|
+
//#region src/home-connect/home-connect-sign-in-result.ts
|
|
1418
|
+
function homeConnectFinishResult(account) {
|
|
1419
|
+
const { rows, secrets } = splitApplianceSecrets(account.appliances.map((app) => ({
|
|
1420
|
+
haId: app.haId,
|
|
1421
|
+
name: app.name,
|
|
1422
|
+
type: app.type,
|
|
1423
|
+
host: app.connection.host,
|
|
1424
|
+
psk: app.connection.psk,
|
|
1425
|
+
mode: app.connection.mode,
|
|
1426
|
+
...app.connection.iv64.length > 0 ? { iv64: app.connection.iv64 } : {},
|
|
1427
|
+
...parseLoginDeviceDescription(app.connection.deviceDescription)
|
|
1428
|
+
})));
|
|
1429
|
+
return {
|
|
1430
|
+
token: account.token,
|
|
1431
|
+
...account.refresh.length > 0 ? { refresh: account.refresh } : {},
|
|
1432
|
+
accountLabel: account.accountId,
|
|
1433
|
+
appliances: JSON.stringify(rows),
|
|
1434
|
+
applianceSecrets: JSON.stringify(secrets),
|
|
1435
|
+
summary: account.summary
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
//#endregion
|
|
1439
|
+
//#region src/home-connect/home-connect-sign-in-proxy.ts
|
|
1440
|
+
/**
|
|
1441
|
+
* Home Connect signs in through its own identity pages, which end on `hcauth://auth/prod?...`
|
|
1442
|
+
* — an address no browser can open. These are the hosts those pages are served from; the
|
|
1443
|
+
* proxy reaches nothing else. (Not verified against the live pages: no vendor credentials.)
|
|
1444
|
+
*/
|
|
1445
|
+
var HOME_CONNECT_LOGIN_HOSTS = ["api.home-connect.com", "singlekey-id.com"];
|
|
1446
|
+
var HOME_CONNECT_PROXY_PREFIX = "login";
|
|
1447
|
+
var HOME_CONNECT_APP_SCHEME = HOME_CONNECT_REDIRECT_URI.slice(0, HOME_CONNECT_REDIRECT_URI.indexOf("://") + 3);
|
|
1448
|
+
var homeConnectSignInClaims = require_account_integration_offline.createSignInClaims();
|
|
1449
|
+
/** Start an attempt whose `authorizeUrl` is the hub-relative proxied login page. */
|
|
1450
|
+
function beginHomeConnectProxiedSignIn(deps) {
|
|
1451
|
+
const nowMs = deps.nowMs ?? (() => Date.now());
|
|
1452
|
+
const published = publishHomeConnectSignIn(homeConnectSignInAttempts, nowMs());
|
|
1453
|
+
if (deps.proxy === null) return published;
|
|
1454
|
+
const entry = require_account_integration_offline.openSignInProxy({
|
|
1455
|
+
proxy: deps.proxy,
|
|
1456
|
+
claims: homeConnectSignInClaims,
|
|
1457
|
+
addonId: deps.addonId,
|
|
1458
|
+
attemptId: published.attemptId,
|
|
1459
|
+
authorizeUrl: published.authorizeUrl,
|
|
1460
|
+
hosts: HOME_CONNECT_LOGIN_HOSTS,
|
|
1461
|
+
appScheme: HOME_CONNECT_APP_SCHEME,
|
|
1462
|
+
finish: async (callback) => homeConnectFinishResult(await finishHomeConnectSignIn({
|
|
1463
|
+
attemptId: published.attemptId,
|
|
1464
|
+
callback,
|
|
1465
|
+
nowMs: nowMs()
|
|
1466
|
+
}, homeConnectSignInAttempts, deps.fetchImpl)),
|
|
1467
|
+
nowMs
|
|
1468
|
+
});
|
|
1469
|
+
return {
|
|
1470
|
+
attemptId: published.attemptId,
|
|
1471
|
+
authorizeUrl: entry
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
//#endregion
|
|
1475
|
+
//#region src/home-connect/home-connect-discovery.ts
|
|
1476
|
+
/** Dishwasher accessory label: `Dishwasher` plus the appliance name. */
|
|
1477
|
+
function homeConnectDishwasherEntityName(containerName) {
|
|
1478
|
+
const base = containerName.trim();
|
|
1479
|
+
return base.length > 0 ? `Dishwasher ${base}` : "Dishwasher";
|
|
1480
|
+
}
|
|
1481
|
+
function deviceTypeForApplianceType(type) {
|
|
1482
|
+
if (type.toLowerCase().includes("dishwasher")) return require_dist.DeviceType.Container;
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* The container candidate for one appliance. Its sensor children are not listed here:
|
|
1487
|
+
* they are the entities the appliance's own description declares, known once it is
|
|
1488
|
+
* connected, and they are attached to the container as that description is read.
|
|
1489
|
+
*/
|
|
1490
|
+
function dishwasherContainerCandidate(app, adoptedId) {
|
|
1491
|
+
const name = app.name.length > 0 ? app.name : app.haId;
|
|
1492
|
+
const children = [{
|
|
1493
|
+
childNativeId: `${app.haId}#dishwasher`,
|
|
1494
|
+
name: homeConnectDishwasherEntityName(name),
|
|
1495
|
+
type: require_dist.DeviceType.Dishwasher,
|
|
1496
|
+
status: "online",
|
|
1497
|
+
metadata: {
|
|
1498
|
+
serialNumber: app.haId,
|
|
1499
|
+
model: app.type
|
|
1500
|
+
},
|
|
1501
|
+
alreadyAdopted: false,
|
|
1502
|
+
adoptedDeviceId: null,
|
|
1503
|
+
capabilities: ["dishwasher"]
|
|
1504
|
+
}];
|
|
1505
|
+
return {
|
|
1506
|
+
childNativeId: app.haId,
|
|
1507
|
+
name,
|
|
1508
|
+
type: require_dist.DeviceType.Container,
|
|
1509
|
+
status: "online",
|
|
1510
|
+
metadata: {
|
|
1511
|
+
serialNumber: app.haId,
|
|
1512
|
+
model: app.type
|
|
1513
|
+
},
|
|
1514
|
+
alreadyAdopted: adoptedId !== null,
|
|
1515
|
+
adoptedDeviceId: adoptedId,
|
|
1516
|
+
capabilities: [],
|
|
1517
|
+
children
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function buildHomeConnectCandidates(input) {
|
|
1521
|
+
const out = [];
|
|
1522
|
+
for (const app of input.appliances) {
|
|
1523
|
+
if (deviceTypeForApplianceType(app.type) === null) continue;
|
|
1524
|
+
const adoptedId = input.adopted.get(app.haId) ?? null;
|
|
1525
|
+
out.push(dishwasherContainerCandidate(app, adoptedId));
|
|
1526
|
+
}
|
|
1527
|
+
return out;
|
|
1528
|
+
}
|
|
1529
|
+
//#endregion
|
|
1530
|
+
//#region src/home-connect/home-connect-adoption-provider.ts
|
|
1531
|
+
var DEVICES_FILTER = {
|
|
1532
|
+
id: "devices",
|
|
1533
|
+
label: "Devices",
|
|
1534
|
+
isDefault: true
|
|
1535
|
+
};
|
|
1536
|
+
async function adoptedMapForIntegration(integrationId, listAdopted) {
|
|
1537
|
+
const all = await listAdopted();
|
|
1538
|
+
const map = /* @__PURE__ */ new Map();
|
|
1539
|
+
for (const device of all) if (device.config["system"] === "home-connect" && device.config["brokerId"] === integrationId) {
|
|
1540
|
+
const haId = device.config["haId"];
|
|
1541
|
+
if (typeof haId === "string") map.set(haId, device.id);
|
|
1542
|
+
}
|
|
1543
|
+
return map;
|
|
1544
|
+
}
|
|
1545
|
+
function buildHomeConnectAdoptionProvider(deps) {
|
|
1546
|
+
const { registry, hasIntegration, listIntegrations, listAdopted, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
|
|
1547
|
+
async function allCandidatesForIntegration(integrationId) {
|
|
1548
|
+
return buildHomeConnectCandidates({
|
|
1549
|
+
appliances: registry.appliances(integrationId),
|
|
1550
|
+
adopted: await adoptedMapForIntegration(integrationId, listAdopted)
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
listCandidateFilters: async () => ({ filters: [DEVICES_FILTER] }),
|
|
1555
|
+
listCandidates: async ({ integrationId, page, pageSize }) => {
|
|
1556
|
+
const cands = await allCandidatesForIntegration(integrationId);
|
|
1557
|
+
const start = (page - 1) * pageSize;
|
|
1558
|
+
return {
|
|
1559
|
+
candidates: cands.slice(start, start + pageSize),
|
|
1560
|
+
totalCount: cands.length,
|
|
1561
|
+
page,
|
|
1562
|
+
pageSize
|
|
1563
|
+
};
|
|
1564
|
+
},
|
|
1565
|
+
getCandidate: async ({ integrationId, childNativeId }) => {
|
|
1566
|
+
return (await allCandidatesForIntegration(integrationId)).find((c) => c.childNativeId === childNativeId) ?? null;
|
|
1567
|
+
},
|
|
1568
|
+
getStatus: async () => {
|
|
1569
|
+
try {
|
|
1570
|
+
let candidateCount = 0;
|
|
1571
|
+
for (const integrationId of listIntegrations()) candidateCount += (await allCandidatesForIntegration(integrationId)).length;
|
|
1572
|
+
const adoptedCount = (await listAdopted()).filter((d) => d.config["system"] === "home-connect").length;
|
|
1573
|
+
return {
|
|
1574
|
+
lastDiscoveryAt: Date.now(),
|
|
1575
|
+
candidateCount,
|
|
1576
|
+
adoptedCount,
|
|
1577
|
+
lastError: null
|
|
1578
|
+
};
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
return {
|
|
1581
|
+
lastDiscoveryAt: null,
|
|
1582
|
+
candidateCount: 0,
|
|
1583
|
+
adoptedCount: 0,
|
|
1584
|
+
lastError: require_dist.errMsg(err)
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
refresh: async ({ integrationId }) => {
|
|
1589
|
+
const candidateCount = (await allCandidatesForIntegration(integrationId)).length;
|
|
1590
|
+
const adoptedCount = (await adoptedMapForIntegration(integrationId, listAdopted)).size;
|
|
1591
|
+
return {
|
|
1592
|
+
lastDiscoveryAt: Date.now(),
|
|
1593
|
+
candidateCount,
|
|
1594
|
+
adoptedCount,
|
|
1595
|
+
lastError: null
|
|
1596
|
+
};
|
|
1597
|
+
},
|
|
1598
|
+
adopt: async ({ integrationId, childNativeIds, perCandidate }) => {
|
|
1599
|
+
if (!registry.has(integrationId)) throw new Error(`home-connect adopt: integration ${integrationId} not connected`);
|
|
1600
|
+
const appliances = registry.appliances(integrationId);
|
|
1601
|
+
const adopted = [];
|
|
1602
|
+
let failures = 0;
|
|
1603
|
+
for (const haId of childNativeIds) try {
|
|
1604
|
+
const app = appliances.find((a) => a.haId === haId);
|
|
1605
|
+
if (!app) {
|
|
1606
|
+
failures++;
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
const candidate = buildHomeConnectCandidates({
|
|
1610
|
+
appliances: [app],
|
|
1611
|
+
adopted: /* @__PURE__ */ new Map()
|
|
1612
|
+
})[0];
|
|
1613
|
+
if (!candidate) {
|
|
1614
|
+
failures++;
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
const name = perCandidate?.[haId]?.name ?? app.name;
|
|
1618
|
+
const { deviceId } = await adoptDevice({
|
|
1619
|
+
haId,
|
|
1620
|
+
integrationId,
|
|
1621
|
+
type: candidate.type,
|
|
1622
|
+
name,
|
|
1623
|
+
applianceType: app.type
|
|
1624
|
+
});
|
|
1625
|
+
adopted.push({
|
|
1626
|
+
childNativeId: haId,
|
|
1627
|
+
parentDeviceId: deviceId
|
|
1628
|
+
});
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
logger.warn("home-connect adopt failed", { meta: {
|
|
1631
|
+
haId,
|
|
1632
|
+
integrationId,
|
|
1633
|
+
error: require_dist.errMsg(err)
|
|
1634
|
+
} });
|
|
1635
|
+
failures++;
|
|
1636
|
+
}
|
|
1637
|
+
if (adopted.length === 0 && failures > 0) throw new Error(`home-connect adopt: all ${failures} adopt(s) failed`);
|
|
1638
|
+
return { adopted: adopted.map((a) => ({
|
|
1639
|
+
...a,
|
|
1640
|
+
accessoryDeviceIds: []
|
|
1641
|
+
})) };
|
|
1642
|
+
},
|
|
1643
|
+
release: async ({ camDeviceId }) => {
|
|
1644
|
+
await removeDevice(camDeviceId);
|
|
1645
|
+
},
|
|
1646
|
+
resync: async ({ camDeviceId }) => {
|
|
1647
|
+
const cfg = await findDeviceConfig(camDeviceId);
|
|
1648
|
+
if (!cfg || cfg["system"] !== "home-connect") throw new Error(`home-connect resync: device ${camDeviceId} not found`);
|
|
1649
|
+
const integrationId = String(cfg["brokerId"]);
|
|
1650
|
+
const haId = String(cfg["haId"]);
|
|
1651
|
+
if (!hasIntegration(integrationId)) throw new Error(`home-connect resync: integration ${integrationId} not connected`);
|
|
1652
|
+
if (!registry.appliances(integrationId).some((a) => a.haId === haId)) throw new Error(`home-connect resync: appliance ${haId} no longer present`);
|
|
1653
|
+
return {
|
|
1654
|
+
changed: false,
|
|
1655
|
+
rebuiltChildren: 0
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
//#endregion
|
|
1540
1661
|
//#region src/home-connect/home-connect-gateway.ts
|
|
1541
1662
|
/**
|
|
1542
1663
|
* Where devices find the client of their integration. A device that started before
|
|
@@ -3119,51 +3240,6 @@ function unmapped(appliances) {
|
|
|
3119
3240
|
return appliances.filter((a) => a.mode === "TLS" && entityCountInStoredDescription(a.deviceDescription) === 0).map((a) => a.haId);
|
|
3120
3241
|
}
|
|
3121
3242
|
//#endregion
|
|
3122
|
-
//#region src/home-connect/home-connect-stored-appliance.ts
|
|
3123
|
-
function stubDeviceDescriptionObject(row) {
|
|
3124
|
-
return { info: {
|
|
3125
|
-
deviceID: row.haId,
|
|
3126
|
-
deviceType: row.type,
|
|
3127
|
-
model: row.name
|
|
3128
|
-
} };
|
|
3129
|
-
}
|
|
3130
|
-
function stubDeviceDescription(row) {
|
|
3131
|
-
return JSON.stringify(stubDeviceDescriptionObject(row));
|
|
3132
|
-
}
|
|
3133
|
-
function serializedDeviceDescription(row) {
|
|
3134
|
-
if (row.deviceDescription === void 0) return stubDeviceDescription(row);
|
|
3135
|
-
if (typeof row.deviceDescription === "string") return row.deviceDescription;
|
|
3136
|
-
return JSON.stringify(row.deviceDescription);
|
|
3137
|
-
}
|
|
3138
|
-
function isDescriptionRecord(value) {
|
|
3139
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3140
|
-
}
|
|
3141
|
-
function parseLoginDeviceDescription(raw) {
|
|
3142
|
-
const trimmed = raw.trim();
|
|
3143
|
-
if (trimmed.length === 0) return {};
|
|
3144
|
-
try {
|
|
3145
|
-
const parsed = JSON.parse(trimmed);
|
|
3146
|
-
if (isDescriptionRecord(parsed)) return { deviceDescription: parsed };
|
|
3147
|
-
} catch {
|
|
3148
|
-
return {};
|
|
3149
|
-
}
|
|
3150
|
-
return {};
|
|
3151
|
-
}
|
|
3152
|
-
function listedApplianceFromStored(row) {
|
|
3153
|
-
return {
|
|
3154
|
-
haId: row.haId,
|
|
3155
|
-
name: row.name,
|
|
3156
|
-
type: row.type,
|
|
3157
|
-
connection: {
|
|
3158
|
-
host: row.host,
|
|
3159
|
-
psk: row.psk,
|
|
3160
|
-
mode: row.mode,
|
|
3161
|
-
iv64: row.iv64 ?? "",
|
|
3162
|
-
deviceDescription: serializedDeviceDescription(row)
|
|
3163
|
-
}
|
|
3164
|
-
};
|
|
3165
|
-
}
|
|
3166
|
-
//#endregion
|
|
3167
3243
|
//#region src/home-connect/home-connect-integration-manager.ts
|
|
3168
3244
|
/** Delays between IDDF fetch retries for an appliance that has no entity map; the last repeats. */
|
|
3169
3245
|
var HOME_CONNECT_IDDF_RETRY_MS = [
|
|
@@ -5243,11 +5319,14 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
|
|
|
5243
5319
|
};
|
|
5244
5320
|
clients = null;
|
|
5245
5321
|
reconciler = null;
|
|
5322
|
+
loginProxy = null;
|
|
5323
|
+
loginPlane = null;
|
|
5246
5324
|
constructor() {
|
|
5247
5325
|
super({});
|
|
5248
5326
|
}
|
|
5249
5327
|
async onInitialize() {
|
|
5250
5328
|
const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
|
|
5329
|
+
await this.serveLoginProxy();
|
|
5251
5330
|
this.clients = new HomeConnectClientRegistry(this.ctx.logger, { onConnectionUpdated: (integrationId, connection) => {
|
|
5252
5331
|
this.persistIntegrationAppliances(integrationId, connection);
|
|
5253
5332
|
} });
|
|
@@ -5277,32 +5356,22 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
|
|
|
5277
5356
|
customActions: homeConnectActions,
|
|
5278
5357
|
actionHandlers: {
|
|
5279
5358
|
beginSignIn: async () => ({
|
|
5280
|
-
...
|
|
5281
|
-
|
|
5359
|
+
...beginHomeConnectProxiedSignIn({
|
|
5360
|
+
proxy: this.loginProxy,
|
|
5361
|
+
addonId: this.ctx.id
|
|
5362
|
+
}),
|
|
5363
|
+
summary: "Open the sign-in page and sign in."
|
|
5282
5364
|
}),
|
|
5283
|
-
finishSignIn: async (input) => {
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
const
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
host: app.connection.host,
|
|
5294
|
-
psk: app.connection.psk,
|
|
5295
|
-
mode: app.connection.mode,
|
|
5296
|
-
...app.connection.iv64.length > 0 ? { iv64: app.connection.iv64 } : {},
|
|
5297
|
-
...parseLoginDeviceDescription(app.connection.deviceDescription)
|
|
5298
|
-
})));
|
|
5299
|
-
return {
|
|
5300
|
-
token: account.token,
|
|
5301
|
-
...account.refresh.length > 0 ? { refresh: account.refresh } : {},
|
|
5302
|
-
accountLabel: account.accountId,
|
|
5303
|
-
appliances: JSON.stringify(rows),
|
|
5304
|
-
applianceSecrets: JSON.stringify(secrets),
|
|
5305
|
-
summary: account.summary
|
|
5365
|
+
finishSignIn: async (input) => homeConnectFinishResult(await finishHomeConnectSignIn({
|
|
5366
|
+
attemptId: input.attemptId,
|
|
5367
|
+
callback: input.callback,
|
|
5368
|
+
nowMs: Date.now()
|
|
5369
|
+
}, homeConnectSignInAttempts)),
|
|
5370
|
+
claimSignIn: async (input) => {
|
|
5371
|
+
const claim = homeConnectSignInClaims.claim(input.attemptId, Date.now());
|
|
5372
|
+
return claim.state === "pending" ? { pending: true } : {
|
|
5373
|
+
pending: false,
|
|
5374
|
+
...claim.result
|
|
5306
5375
|
};
|
|
5307
5376
|
},
|
|
5308
5377
|
signOut: async () => ({
|
|
@@ -5312,7 +5381,28 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
|
|
|
5312
5381
|
}
|
|
5313
5382
|
};
|
|
5314
5383
|
}
|
|
5384
|
+
/** The hub's HTTPS face for the vendor's login pages (D955): admin session only. */
|
|
5385
|
+
async serveLoginProxy() {
|
|
5386
|
+
const proxy = new require_dist.VendorLoginProxy({
|
|
5387
|
+
addonId: this.ctx.id,
|
|
5388
|
+
prefix: HOME_CONNECT_PROXY_PREFIX,
|
|
5389
|
+
logger: this.ctx.logger
|
|
5390
|
+
});
|
|
5391
|
+
try {
|
|
5392
|
+
this.loginPlane = await this.ctx.dataPlane?.serve({
|
|
5393
|
+
prefix: "login",
|
|
5394
|
+
access: "admin",
|
|
5395
|
+
handler: proxy.handler
|
|
5396
|
+
}) ?? null;
|
|
5397
|
+
if (this.loginPlane !== null) this.loginProxy = proxy;
|
|
5398
|
+
} catch (err) {
|
|
5399
|
+
this.ctx.logger.warn("Home Connect login proxy failed to serve — sign-in falls back to paste", { meta: { error: require_dist.errMsg(err) } });
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5315
5402
|
async onShutdown() {
|
|
5403
|
+
await this.loginPlane?.dispose().catch(() => void 0);
|
|
5404
|
+
this.loginPlane = null;
|
|
5405
|
+
this.loginProxy = null;
|
|
5316
5406
|
this.reconciler?.stop();
|
|
5317
5407
|
this.reconciler = null;
|
|
5318
5408
|
try {
|