@camstack/addon-provider-bosch 0.1.20 → 0.1.22

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,5 +1,5 @@
1
- import { C as customAction, D as _enum, E as isSecretMarkerLike, F as unknown, I as EventCategory, M as record, N as string, O as array, P as union, S as createEvent, T as isSecretMarker, a as binaryCapability, b as DeviceType, c as consumablesCapability, d as enumSensorCapability, f as eventEmitterCapability, h as powerMeterCapability, j as object, k as literal, l as deviceAdoptionCapability, m as numericSensorCapability, n as BaseDeviceProvider, r as DishwasherOperationStateSchema, s as connectionTestCapability, t as BaseDevice, u as dishwasherCapability, w as defineCustomActions, x as errMsg, y as DeviceRole } from "../dist-B75tWQLF.mjs";
2
- import { n as cascadeRemoveDevicesForIntegration, r as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-DvuqXgc8.mjs";
1
+ import { A as _enum, C as DeviceType, D as defineCustomActions, E as customAction, F as record, I as string, L as union, M as literal, O as isSecretMarker, P as object, R as unknown, S as DeviceRole, T as createEvent, _ as runningFromDishwasherOperation, c as connectionTestCapability, d as dishwasherCapability, f as enumSensorCapability, g as powerMeterCapability, h as numericSensorCapability, i as RUNNING_STATE_COLD, j as array, k as isSecretMarkerLike, l as consumablesCapability, n as BaseDeviceProvider, o as binaryCapability, p as eventEmitterCapability, r as DishwasherOperationStateSchema, t as BaseDevice, u as deviceAdoptionCapability, v as runningStateCapability, w as errMsg, z as EventCategory } from "../dist-DGlq0nuG.mjs";
2
+ import { a as parseOAuthCallback, i as loginFailureForAttempt, n as cascadeRemoveDevicesForIntegration, r as createSignInAttemptStore, t as setIntegrationDevicesOnline } from "../account-integration-offline-34gaYDfb.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { connect } from "node:tls";
5
5
  import { inflateRawSync } from "node:zlib";
@@ -7,21 +7,20 @@ import { lookup } from "node:dns/promises";
7
7
  //#region src/home-connect/custom-actions.ts
8
8
  var homeConnectActions = defineCustomActions({
9
9
  beginSignIn: customAction(object({}), object({
10
+ attemptId: string().min(1),
10
11
  authorizeUrl: string().min(1),
11
- verifier: string().min(1),
12
- state: string().min(1),
13
12
  summary: string()
14
13
  }), {
15
14
  kind: "mutation",
16
15
  auth: "admin"
17
16
  }),
18
17
  finishSignIn: customAction(object({
19
- verifier: string().min(1),
20
- callback: string().min(1),
21
- state: string().min(1)
18
+ attemptId: string().min(1),
19
+ callback: string().min(1)
22
20
  }), object({
23
21
  token: string().min(1).meta({ secret: true }),
24
22
  refresh: string().optional().meta({ secret: true }),
23
+ accountLabel: string(),
25
24
  /** JSON of the paired appliances — no credential on it. */
26
25
  appliances: string().min(1),
27
26
  /** JSON of `haId -> { psk, iv64? }`; stored in the keychain, never in `appliances`. */
@@ -30,6 +29,13 @@ var homeConnectActions = defineCustomActions({
30
29
  }), {
31
30
  kind: "mutation",
32
31
  auth: "admin"
32
+ }),
33
+ signOut: customAction(object({}), object({
34
+ signedOut: literal(true),
35
+ summary: string()
36
+ }), {
37
+ kind: "mutation",
38
+ auth: "admin"
33
39
  })
34
40
  });
35
41
  //#endregion
@@ -601,11 +607,15 @@ var HOME_CONNECT_IDDF_URL = (haId) => `https://eu.services.home-connect.com/api/
601
607
  //#endregion
602
608
  //#region src/home-connect/home-connect-login.ts
603
609
  /** Public OAuth client ids from the Home Connect app login (hcpy). */
604
- var APP_ID$1 = "9B75AC9EC512F36C84256AC47D813E2C1DD0D6520DF774B020E1E6E2EB29B1F3";
605
- var OAUTH_BASE = "https://api.home-connect.com/security/oauth/";
610
+ var HOME_CONNECT_CLIENT_ID = "9B75AC9EC512F36C84256AC47D813E2C1DD0D6520DF774B020E1E6E2EB29B1F3";
611
+ var HOME_CONNECT_OAUTH_BASE = "https://api.home-connect.com/security/oauth/";
606
612
  var ASSET_BASE = "https://eu.services.home-connect.com/";
607
- var REDIRECT_URI = "hcauth://auth/prod";
608
- var AUTHORIZE_SCOPE = "Control DeleteAppliance IdentifyAppliance Images Monitor ReadAccount ReadOrigApi Settings WriteAppliance WriteOrigApi";
613
+ var HOME_CONNECT_REDIRECT_URI = "hcauth://auth/prod";
614
+ var HOME_CONNECT_AUTHORIZE_SCOPE = "Control DeleteAppliance IdentifyAppliance Images Monitor ReadAccount ReadOrigApi Settings WriteAppliance WriteOrigApi";
615
+ var APP_ID$1 = HOME_CONNECT_CLIENT_ID;
616
+ var OAUTH_BASE = HOME_CONNECT_OAUTH_BASE;
617
+ var REDIRECT_URI = HOME_CONNECT_REDIRECT_URI;
618
+ var AUTHORIZE_SCOPE = HOME_CONNECT_AUTHORIZE_SCOPE;
609
619
  var CookieJar = class {
610
620
  #cookies = /* @__PURE__ */ new Map();
611
621
  absorb(headers) {
@@ -811,6 +821,7 @@ async function fetchHomeConnectAccount(creds, fetchImpl = homeConnectLoginFetch)
811
821
  token,
812
822
  refresh,
813
823
  appliances,
824
+ accountId: homeConnectAccountLabel(token),
814
825
  summary: accountSummary(appliances)
815
826
  };
816
827
  } catch (err) {
@@ -823,9 +834,18 @@ async function fetchHomeConnectAccount(creds, fetchImpl = homeConnectLoginFetch)
823
834
  token: next.token,
824
835
  refresh: next.refresh,
825
836
  appliances,
837
+ accountId: homeConnectAccountLabel(next.token),
826
838
  summary: accountSummary(appliances)
827
839
  };
828
840
  }
841
+ /** The JWT `sub`, or empty when the token has none. Never throws. */
842
+ function homeConnectAccountLabel(token) {
843
+ try {
844
+ return accountIdFromAccessToken(token);
845
+ } catch {
846
+ return "";
847
+ }
848
+ }
829
849
  /**
830
850
  * Exchange the refresh token for a new access token. Never throws; a failure says why and
831
851
  * carries no credential. A response without a new refresh token keeps the one that was used.
@@ -948,6 +968,24 @@ async function completeHomeConnectSignIn(input, fetchImpl = homeConnectLoginFetc
948
968
  }
949
969
  return fetchHomeConnectAccount(tokensFromBody(token.body), fetchImpl);
950
970
  }
971
+ /** What the form is allowed to see. The verifier stays in `store`. */
972
+ function publishHomeConnectSignIn(store, nowMs) {
973
+ const started = beginHomeConnectSignIn();
974
+ return {
975
+ attemptId: store.put(started.verifier, started.state, nowMs),
976
+ authorizeUrl: started.authorizeUrl
977
+ };
978
+ }
979
+ async function finishHomeConnectSignIn(input, store, fetchImpl = homeConnectLoginFetch) {
980
+ const taken = store.take(input.attemptId, input.nowMs);
981
+ if (!taken.ok) throw new HomeConnectLoginError(loginFailureForAttempt(taken.reason));
982
+ return completeHomeConnectSignIn({
983
+ verifier: taken.attempt.verifier,
984
+ callback: input.callback,
985
+ state: taken.attempt.state
986
+ }, fetchImpl);
987
+ }
988
+ var homeConnectSignInAttempts = createSignInAttemptStore();
951
989
  //#endregion
952
990
  //#region src/home-connect/home-connect-discovery.ts
953
991
  /** Dishwasher accessory label: `Dishwasher` plus the appliance name. */
@@ -1191,7 +1229,9 @@ var homeConnectStoredApplianceSchema = homeConnectApplianceRowSchema.extend({
1191
1229
  var homeConnectConfigSchema = object({
1192
1230
  token: string().min(1).describe("Home Connect access token").meta({ secret: true }),
1193
1231
  refresh: string().optional().describe("Home Connect refresh token").meta({ secret: true }),
1194
- appliances: array(homeConnectStoredApplianceSchema).optional()
1232
+ appliances: array(homeConnectStoredApplianceSchema).optional(),
1233
+ /** Shown on the sign-in field. The JWT subject, not a credential. */
1234
+ accountLabel: string().optional()
1195
1235
  });
1196
1236
  function parseJsonValue(raw) {
1197
1237
  if (typeof raw !== "string") return raw;
@@ -1392,103 +1432,49 @@ function sameHomeConnectConnection(a, b) {
1392
1432
  var HOME_CONNECT_PAIRED_ONLY = "__paired_only__";
1393
1433
  function buildHomeConnectFormSchema() {
1394
1434
  const pairedOnly = {
1395
- field: "authorizeUrl",
1435
+ field: "__hidden__",
1396
1436
  equals: HOME_CONNECT_PAIRED_ONLY
1397
1437
  };
1398
1438
  return { sections: [{
1399
1439
  id: "gateway",
1400
1440
  title: "Home Connect",
1401
1441
  titleKey: "provider-bosch:home-connect.s_gateway.title",
1402
- description: "Open the login link, sign in, then paste the address the browser stops on (hcauth://auth/prod?code=…). Chrome: DevTools > Network > last redirect > Location.",
1442
+ description: "Sign in with your Home Connect account. Paste the address the browser stops on (hcauth://auth/prod?code=…).",
1403
1443
  descriptionKey: "provider-bosch:home-connect.s_gateway.description",
1404
1444
  columns: 1,
1405
1445
  fields: [
1406
1446
  {
1407
- type: "addon-action-button",
1447
+ type: "account-sign-in",
1408
1448
  addonId: "bosch-home-connect",
1409
- key: "__home-connect-begin",
1449
+ key: "__home-connect-sign-in",
1410
1450
  label: "Home Connect",
1411
- labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-begin.label",
1412
- buttonLabel: "Create login link",
1413
- action: "beginSignIn",
1414
- variant: "primary",
1415
- assignResult: {
1416
- authorizeUrl: "authorizeUrl",
1417
- verifier: "verifier",
1418
- state: "state"
1419
- },
1420
- successMessage: "Open the link in Chrome and sign in."
1421
- },
1422
- {
1423
- type: "textarea",
1424
- key: "authorizeUrl",
1425
- label: "Login link",
1426
- labelKey: "provider-bosch:home-connect.s_gateway.authorizeUrl.label",
1427
- required: false,
1428
- omitFromSettings: true
1429
- },
1430
- {
1431
- type: "textarea",
1432
- key: "callback",
1433
- label: "Redirect address or code",
1434
- labelKey: "provider-bosch:home-connect.s_gateway.callback.label",
1435
- required: false,
1436
- placeholder: "hcauth://auth/prod?code=…&state=…",
1437
- omitFromSettings: true
1438
- },
1439
- {
1440
- type: "password",
1441
- key: "state",
1442
- label: "Login state",
1443
- labelKey: "provider-bosch:home-connect.s_gateway.state.label",
1444
- required: false,
1445
- omitFromSettings: true,
1446
- showWhen: pairedOnly
1447
- },
1448
- {
1449
- type: "password",
1450
- key: "verifier",
1451
- label: "Login verifier",
1452
- labelKey: "provider-bosch:home-connect.s_gateway.verifier.label",
1453
- required: false,
1454
- omitFromSettings: true,
1455
- showWhen: pairedOnly
1456
- },
1457
- {
1458
- type: "addon-action-button",
1459
- addonId: "bosch-home-connect",
1460
- key: "__home-connect-finish",
1461
- label: "Finish",
1462
- labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-finish.label",
1463
- buttonLabel: "Complete sign-in",
1464
- action: "finishSignIn",
1465
- variant: "primary",
1466
- paramsFromForm: {
1467
- verifier: "verifier",
1468
- callback: "callback",
1469
- state: "state"
1470
- },
1451
+ labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-sign-in.label",
1452
+ providerLabel: "Home Connect",
1453
+ beginAction: "beginSignIn",
1454
+ completeAction: "finishSignIn",
1455
+ signOutAction: "signOut",
1456
+ accountKey: "accountLabel",
1457
+ countFromKey: "appliances",
1458
+ countNoun: "appliance",
1459
+ pastePlaceholder: "hcauth://auth/prod?code=…&state=…",
1460
+ secretKeys: [
1461
+ "token",
1462
+ "refresh",
1463
+ APPLIANCE_SECRETS_KEY
1464
+ ],
1471
1465
  assignResult: {
1472
1466
  token: "token",
1473
1467
  refresh: "refresh",
1468
+ accountLabel: "accountLabel",
1474
1469
  appliances: "appliances",
1475
1470
  [APPLIANCE_SECRETS_KEY]: APPLIANCE_SECRETS_KEY
1476
- },
1477
- successMessage: "{summary}"
1478
- },
1479
- {
1480
- type: "password",
1481
- key: "token",
1482
- label: "Access token",
1483
- labelKey: "provider-bosch:home-connect.s_gateway.token.label",
1484
- required: true,
1485
- showWhen: pairedOnly
1471
+ }
1486
1472
  },
1487
1473
  {
1488
- type: "password",
1489
- key: "refresh",
1490
- label: "Refresh token",
1491
- labelKey: "provider-bosch:home-connect.s_gateway.refresh.label",
1474
+ type: "text",
1475
+ key: "accountLabel",
1476
+ label: "Account",
1477
+ labelKey: "provider-bosch:home-connect.s_gateway.accountLabel.label",
1492
1478
  required: false,
1493
1479
  showWhen: pairedOnly
1494
1480
  },
@@ -1497,15 +1483,38 @@ function buildHomeConnectFormSchema() {
1497
1483
  key: "appliances",
1498
1484
  label: "Appliances",
1499
1485
  labelKey: "provider-bosch:home-connect.s_gateway.appliances.label",
1500
- required: true,
1486
+ required: false,
1501
1487
  showWhen: pairedOnly
1502
1488
  },
1503
1489
  {
1504
1490
  type: "password",
1505
1491
  key: APPLIANCE_SECRETS_KEY,
1506
1492
  label: "Appliance keys",
1507
- required: true,
1493
+ required: false,
1508
1494
  showWhen: pairedOnly
1495
+ },
1496
+ {
1497
+ type: "group",
1498
+ key: "__home-connect-advanced",
1499
+ label: "Advanced",
1500
+ labelKey: "provider-bosch:home-connect.s_gateway.__home-connect-advanced.label",
1501
+ style: "accordion",
1502
+ defaultCollapsed: true,
1503
+ fields: [{
1504
+ type: "password",
1505
+ key: "token",
1506
+ label: "Access token",
1507
+ labelKey: "provider-bosch:home-connect.s_gateway.token.label",
1508
+ required: false,
1509
+ showToggle: false
1510
+ }, {
1511
+ type: "password",
1512
+ key: "refresh",
1513
+ label: "Refresh token",
1514
+ labelKey: "provider-bosch:home-connect.s_gateway.refresh.label",
1515
+ required: false,
1516
+ showToggle: false
1517
+ }]
1509
1518
  }
1510
1519
  ]
1511
1520
  }] };
@@ -4611,6 +4620,7 @@ var homeConnectDishwasherSchema = object({
4611
4620
  });
4612
4621
  var DISH_CAP = "dishwasher";
4613
4622
  var CONSUMABLES_CAP = "consumables";
4623
+ var RUNNING_CAP = "running-state";
4614
4624
  var CONSUMABLES_EMPTY = {
4615
4625
  items: [],
4616
4626
  lastChangedAt: 0,
@@ -4646,7 +4656,9 @@ var HomeConnectDishwasherDevice = class extends BaseDevice {
4646
4656
  await super.onActivate();
4647
4657
  this.registerDishwasherCap();
4648
4658
  this.registerConsumablesCap();
4659
+ this.registerRunningCap();
4649
4660
  this.runtimeState.setCapState(DISH_CAP, COLD_DISHWASHER_STATUS);
4661
+ this.runtimeState.setCapState(RUNNING_CAP, RUNNING_STATE_COLD);
4650
4662
  this.markOnline(false);
4651
4663
  this.#unwatch = homeConnectConnections.watch(this.#brokerId, this.#haId, (event) => this.recomputeSlice(event));
4652
4664
  this.recomputeSlice({ kind: "client" });
@@ -4676,6 +4688,7 @@ var HomeConnectDishwasherDevice = class extends BaseDevice {
4676
4688
  lastValueAt
4677
4689
  });
4678
4690
  this.runtimeState.setCapState(DISH_CAP, slice);
4691
+ this.publishRunningSlice(slice);
4679
4692
  this.writeConsumables(dishwasherConsumablesFromFeatures(view));
4680
4693
  this.markOnline(slice.availability !== "offline");
4681
4694
  }
@@ -4778,6 +4791,15 @@ var HomeConnectDishwasherDevice = class extends BaseDevice {
4778
4791
  setOption: async ({ key, value }) => this.writeKey(this.requireClient(), key, value)
4779
4792
  });
4780
4793
  }
4794
+ registerRunningCap() {
4795
+ this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? RUNNING_STATE_COLD });
4796
+ }
4797
+ publishRunningSlice(slice) {
4798
+ const fetchedAt = Date.now();
4799
+ const previous = this.runtimeState.getCapState(RUNNING_CAP);
4800
+ const next = runningFromDishwasherOperation(slice.operationState, slice.program, previous, fetchedAt, slice.remainingSeconds);
4801
+ this.runtimeState.setCapState(RUNNING_CAP, next);
4802
+ }
4781
4803
  };
4782
4804
  //#endregion
4783
4805
  //#region src/home-connect/devices/home-connect-event-device.ts
@@ -5241,21 +5263,16 @@ var BoschHomeConnectProviderAddon = class extends BaseDeviceProvider {
5241
5263
  ],
5242
5264
  customActions: homeConnectActions,
5243
5265
  actionHandlers: {
5244
- beginSignIn: async () => {
5245
- const started = beginHomeConnectSignIn();
5246
- return {
5247
- authorizeUrl: started.authorizeUrl,
5248
- verifier: started.verifier,
5249
- state: started.state,
5250
- summary: "Open the link in the browser and sign in."
5251
- };
5252
- },
5266
+ beginSignIn: async () => ({
5267
+ ...publishHomeConnectSignIn(homeConnectSignInAttempts, Date.now()),
5268
+ summary: "Open the link in the browser and sign in."
5269
+ }),
5253
5270
  finishSignIn: async (input) => {
5254
- const account = await completeHomeConnectSignIn({
5255
- verifier: input.verifier,
5271
+ const account = await finishHomeConnectSignIn({
5272
+ attemptId: input.attemptId,
5256
5273
  callback: input.callback,
5257
- state: input.state
5258
- });
5274
+ nowMs: Date.now()
5275
+ }, homeConnectSignInAttempts);
5259
5276
  const { rows, secrets } = splitApplianceSecrets(account.appliances.map((app) => ({
5260
5277
  haId: app.haId,
5261
5278
  name: app.name,
@@ -5269,11 +5286,16 @@ var BoschHomeConnectProviderAddon = class extends BaseDeviceProvider {
5269
5286
  return {
5270
5287
  token: account.token,
5271
5288
  ...account.refresh.length > 0 ? { refresh: account.refresh } : {},
5289
+ accountLabel: account.accountId,
5272
5290
  appliances: JSON.stringify(rows),
5273
5291
  applianceSecrets: JSON.stringify(secrets),
5274
5292
  summary: account.summary
5275
5293
  };
5276
- }
5294
+ },
5295
+ signOut: async () => ({
5296
+ signedOut: true,
5297
+ summary: "Signed out."
5298
+ })
5277
5299
  }
5278
5300
  };
5279
5301
  }