@camstack/addon-provider-bosch 0.1.29 → 0.1.31

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.
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("../dist-CyLs51d2.js");
3
- const require_account_integration_offline = require("../account-integration-offline-DMtMWixt.js");
2
+ const require_dist = require("../dist-NdDwxHvT.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-IvvrXQDg.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. Paste the address the browser stops on (hcauth://auth/prod?code=…).",
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,294 @@ 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_APP_SCHEME = HOME_CONNECT_REDIRECT_URI.slice(0, HOME_CONNECT_REDIRECT_URI.indexOf("://") + 3);
1447
+ var homeConnectSignInClaims = require_account_integration_offline.createSignInClaims();
1448
+ /** Start an attempt whose `authorizeUrl` is the hub-relative proxied login page. */
1449
+ function beginHomeConnectProxiedSignIn(deps) {
1450
+ const nowMs = deps.nowMs ?? (() => Date.now());
1451
+ const published = publishHomeConnectSignIn(homeConnectSignInAttempts, nowMs());
1452
+ if (deps.proxy === null) return published;
1453
+ const entry = require_account_integration_offline.openSignInProxy({
1454
+ proxy: deps.proxy,
1455
+ claims: homeConnectSignInClaims,
1456
+ addonId: deps.addonId,
1457
+ attemptId: published.attemptId,
1458
+ authorizeUrl: published.authorizeUrl,
1459
+ hosts: HOME_CONNECT_LOGIN_HOSTS,
1460
+ appScheme: HOME_CONNECT_APP_SCHEME,
1461
+ finish: async (callback) => homeConnectFinishResult(await finishHomeConnectSignIn({
1462
+ attemptId: published.attemptId,
1463
+ callback,
1464
+ nowMs: nowMs()
1465
+ }, homeConnectSignInAttempts, deps.fetchImpl)),
1466
+ nowMs
1467
+ });
1468
+ return {
1469
+ attemptId: published.attemptId,
1470
+ authorizeUrl: entry
1471
+ };
1472
+ }
1473
+ //#endregion
1474
+ //#region src/home-connect/home-connect-discovery.ts
1475
+ /** Dishwasher accessory label: `Dishwasher` plus the appliance name. */
1476
+ function homeConnectDishwasherEntityName(containerName) {
1477
+ const base = containerName.trim();
1478
+ return base.length > 0 ? `Dishwasher ${base}` : "Dishwasher";
1479
+ }
1480
+ function deviceTypeForApplianceType(type) {
1481
+ if (type.toLowerCase().includes("dishwasher")) return require_dist.DeviceType.Container;
1482
+ return null;
1483
+ }
1484
+ /**
1485
+ * The container candidate for one appliance. Its sensor children are not listed here:
1486
+ * they are the entities the appliance's own description declares, known once it is
1487
+ * connected, and they are attached to the container as that description is read.
1488
+ */
1489
+ function dishwasherContainerCandidate(app, adoptedId) {
1490
+ const name = app.name.length > 0 ? app.name : app.haId;
1491
+ const children = [{
1492
+ childNativeId: `${app.haId}#dishwasher`,
1493
+ name: homeConnectDishwasherEntityName(name),
1494
+ type: require_dist.DeviceType.Dishwasher,
1495
+ status: "online",
1496
+ metadata: {
1497
+ serialNumber: app.haId,
1498
+ model: app.type
1499
+ },
1500
+ alreadyAdopted: false,
1501
+ adoptedDeviceId: null,
1502
+ capabilities: ["dishwasher"]
1503
+ }];
1504
+ return {
1505
+ childNativeId: app.haId,
1506
+ name,
1507
+ type: require_dist.DeviceType.Container,
1508
+ status: "online",
1509
+ metadata: {
1510
+ serialNumber: app.haId,
1511
+ model: app.type
1512
+ },
1513
+ alreadyAdopted: adoptedId !== null,
1514
+ adoptedDeviceId: adoptedId,
1515
+ capabilities: [],
1516
+ children
1517
+ };
1518
+ }
1519
+ function buildHomeConnectCandidates(input) {
1520
+ const out = [];
1521
+ for (const app of input.appliances) {
1522
+ if (deviceTypeForApplianceType(app.type) === null) continue;
1523
+ const adoptedId = input.adopted.get(app.haId) ?? null;
1524
+ out.push(dishwasherContainerCandidate(app, adoptedId));
1525
+ }
1526
+ return out;
1527
+ }
1528
+ //#endregion
1529
+ //#region src/home-connect/home-connect-adoption-provider.ts
1530
+ var DEVICES_FILTER = {
1531
+ id: "devices",
1532
+ label: "Devices",
1533
+ isDefault: true
1534
+ };
1535
+ async function adoptedMapForIntegration(integrationId, listAdopted) {
1536
+ const all = await listAdopted();
1537
+ const map = /* @__PURE__ */ new Map();
1538
+ for (const device of all) if (device.config["system"] === "home-connect" && device.config["brokerId"] === integrationId) {
1539
+ const haId = device.config["haId"];
1540
+ if (typeof haId === "string") map.set(haId, device.id);
1541
+ }
1542
+ return map;
1543
+ }
1544
+ function buildHomeConnectAdoptionProvider(deps) {
1545
+ const { registry, hasIntegration, listIntegrations, listAdopted, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
1546
+ async function allCandidatesForIntegration(integrationId) {
1547
+ return buildHomeConnectCandidates({
1548
+ appliances: registry.appliances(integrationId),
1549
+ adopted: await adoptedMapForIntegration(integrationId, listAdopted)
1550
+ });
1551
+ }
1552
+ return {
1553
+ listCandidateFilters: async () => ({ filters: [DEVICES_FILTER] }),
1554
+ listCandidates: async ({ integrationId, page, pageSize }) => {
1555
+ const cands = await allCandidatesForIntegration(integrationId);
1556
+ const start = (page - 1) * pageSize;
1557
+ return {
1558
+ candidates: cands.slice(start, start + pageSize),
1559
+ totalCount: cands.length,
1560
+ page,
1561
+ pageSize
1562
+ };
1563
+ },
1564
+ getCandidate: async ({ integrationId, childNativeId }) => {
1565
+ return (await allCandidatesForIntegration(integrationId)).find((c) => c.childNativeId === childNativeId) ?? null;
1566
+ },
1567
+ getStatus: async () => {
1568
+ try {
1569
+ let candidateCount = 0;
1570
+ for (const integrationId of listIntegrations()) candidateCount += (await allCandidatesForIntegration(integrationId)).length;
1571
+ const adoptedCount = (await listAdopted()).filter((d) => d.config["system"] === "home-connect").length;
1572
+ return {
1573
+ lastDiscoveryAt: Date.now(),
1574
+ candidateCount,
1575
+ adoptedCount,
1576
+ lastError: null
1577
+ };
1578
+ } catch (err) {
1579
+ return {
1580
+ lastDiscoveryAt: null,
1581
+ candidateCount: 0,
1582
+ adoptedCount: 0,
1583
+ lastError: require_dist.errMsg(err)
1584
+ };
1585
+ }
1586
+ },
1587
+ refresh: async ({ integrationId }) => {
1588
+ const candidateCount = (await allCandidatesForIntegration(integrationId)).length;
1589
+ const adoptedCount = (await adoptedMapForIntegration(integrationId, listAdopted)).size;
1590
+ return {
1591
+ lastDiscoveryAt: Date.now(),
1592
+ candidateCount,
1593
+ adoptedCount,
1594
+ lastError: null
1595
+ };
1596
+ },
1597
+ adopt: async ({ integrationId, childNativeIds, perCandidate }) => {
1598
+ if (!registry.has(integrationId)) throw new Error(`home-connect adopt: integration ${integrationId} not connected`);
1599
+ const appliances = registry.appliances(integrationId);
1600
+ const adopted = [];
1601
+ let failures = 0;
1602
+ for (const haId of childNativeIds) try {
1603
+ const app = appliances.find((a) => a.haId === haId);
1604
+ if (!app) {
1605
+ failures++;
1606
+ continue;
1607
+ }
1608
+ const candidate = buildHomeConnectCandidates({
1609
+ appliances: [app],
1610
+ adopted: /* @__PURE__ */ new Map()
1611
+ })[0];
1612
+ if (!candidate) {
1613
+ failures++;
1614
+ continue;
1615
+ }
1616
+ const name = perCandidate?.[haId]?.name ?? app.name;
1617
+ const { deviceId } = await adoptDevice({
1618
+ haId,
1619
+ integrationId,
1620
+ type: candidate.type,
1621
+ name,
1622
+ applianceType: app.type
1623
+ });
1624
+ adopted.push({
1625
+ childNativeId: haId,
1626
+ parentDeviceId: deviceId
1627
+ });
1628
+ } catch (err) {
1629
+ logger.warn("home-connect adopt failed", { meta: {
1630
+ haId,
1631
+ integrationId,
1632
+ error: require_dist.errMsg(err)
1633
+ } });
1634
+ failures++;
1635
+ }
1636
+ if (adopted.length === 0 && failures > 0) throw new Error(`home-connect adopt: all ${failures} adopt(s) failed`);
1637
+ return { adopted: adopted.map((a) => ({
1638
+ ...a,
1639
+ accessoryDeviceIds: []
1640
+ })) };
1641
+ },
1642
+ release: async ({ camDeviceId }) => {
1643
+ await removeDevice(camDeviceId);
1644
+ },
1645
+ resync: async ({ camDeviceId }) => {
1646
+ const cfg = await findDeviceConfig(camDeviceId);
1647
+ if (!cfg || cfg["system"] !== "home-connect") throw new Error(`home-connect resync: device ${camDeviceId} not found`);
1648
+ const integrationId = String(cfg["brokerId"]);
1649
+ const haId = String(cfg["haId"]);
1650
+ if (!hasIntegration(integrationId)) throw new Error(`home-connect resync: integration ${integrationId} not connected`);
1651
+ if (!registry.appliances(integrationId).some((a) => a.haId === haId)) throw new Error(`home-connect resync: appliance ${haId} no longer present`);
1652
+ return {
1653
+ changed: false,
1654
+ rebuiltChildren: 0
1655
+ };
1656
+ }
1657
+ };
1658
+ }
1659
+ //#endregion
1540
1660
  //#region src/home-connect/home-connect-gateway.ts
1541
1661
  /**
1542
1662
  * Where devices find the client of their integration. A device that started before
@@ -3119,51 +3239,6 @@ function unmapped(appliances) {
3119
3239
  return appliances.filter((a) => a.mode === "TLS" && entityCountInStoredDescription(a.deviceDescription) === 0).map((a) => a.haId);
3120
3240
  }
3121
3241
  //#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
3242
  //#region src/home-connect/home-connect-integration-manager.ts
3168
3243
  /** Delays between IDDF fetch retries for an appliance that has no entity map; the last repeats. */
3169
3244
  var HOME_CONNECT_IDDF_RETRY_MS = [
@@ -5243,11 +5318,14 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
5243
5318
  };
5244
5319
  clients = null;
5245
5320
  reconciler = null;
5321
+ loginProxy = null;
5322
+ loginPlane = null;
5246
5323
  constructor() {
5247
5324
  super({});
5248
5325
  }
5249
5326
  async onInitialize() {
5250
5327
  const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
5328
+ await this.serveLoginProxy();
5251
5329
  this.clients = new HomeConnectClientRegistry(this.ctx.logger, { onConnectionUpdated: (integrationId, connection) => {
5252
5330
  this.persistIntegrationAppliances(integrationId, connection);
5253
5331
  } });
@@ -5277,32 +5355,22 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
5277
5355
  customActions: homeConnectActions,
5278
5356
  actionHandlers: {
5279
5357
  beginSignIn: async () => ({
5280
- ...publishHomeConnectSignIn(homeConnectSignInAttempts, Date.now()),
5281
- summary: "Open the link in the browser and sign in."
5358
+ ...beginHomeConnectProxiedSignIn({
5359
+ proxy: this.loginProxy,
5360
+ addonId: this.ctx.id
5361
+ }),
5362
+ summary: "Open the sign-in page and sign in."
5282
5363
  }),
5283
- finishSignIn: async (input) => {
5284
- const account = await finishHomeConnectSignIn({
5285
- attemptId: input.attemptId,
5286
- callback: input.callback,
5287
- nowMs: Date.now()
5288
- }, homeConnectSignInAttempts);
5289
- const { rows, secrets } = splitApplianceSecrets(account.appliances.map((app) => ({
5290
- haId: app.haId,
5291
- name: app.name,
5292
- type: app.type,
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
5364
+ finishSignIn: async (input) => homeConnectFinishResult(await finishHomeConnectSignIn({
5365
+ attemptId: input.attemptId,
5366
+ callback: input.callback,
5367
+ nowMs: Date.now()
5368
+ }, homeConnectSignInAttempts)),
5369
+ claimSignIn: async (input) => {
5370
+ const claim = homeConnectSignInClaims.claim(input.attemptId, Date.now());
5371
+ return claim.state === "pending" ? { pending: true } : {
5372
+ pending: false,
5373
+ ...claim.result
5306
5374
  };
5307
5375
  },
5308
5376
  signOut: async () => ({
@@ -5312,7 +5380,27 @@ var BoschHomeConnectProviderAddon = class extends require_dist.BaseDeviceProvide
5312
5380
  }
5313
5381
  };
5314
5382
  }
5383
+ /** The hub's HTTPS face for the vendor's login pages (D955): admin session only. */
5384
+ async serveLoginProxy() {
5385
+ const proxy = new require_dist.VendorLoginProxy({
5386
+ addonId: this.ctx.id,
5387
+ logger: this.ctx.logger
5388
+ });
5389
+ try {
5390
+ this.loginPlane = await this.ctx.dataPlane?.serve({
5391
+ prefix: "vendor-login",
5392
+ access: "admin",
5393
+ handler: proxy.handler
5394
+ }) ?? null;
5395
+ if (this.loginPlane !== null) this.loginProxy = proxy;
5396
+ } catch (err) {
5397
+ this.ctx.logger.warn("Home Connect login proxy failed to serve — sign-in falls back to paste", { meta: { error: require_dist.errMsg(err) } });
5398
+ }
5399
+ }
5315
5400
  async onShutdown() {
5401
+ await this.loginPlane?.dispose().catch(() => void 0);
5402
+ this.loginPlane = null;
5403
+ this.loginProxy = null;
5316
5404
  this.reconciler?.stop();
5317
5405
  this.reconciler = null;
5318
5406
  try {