@camstack/addon-provider-bosch 0.1.20 → 0.1.21

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 array, C as errMsg, D as isSecretMarker, E as defineCustomActions, F as string, I as union, L as unknown, N as object, O as isSecretMarkerLike, P as record, R as EventCategory, S as DeviceType, T as customAction, _ as runningStateCapability, a as binaryCapability, c as consumablesCapability, d as enumSensorCapability, f as eventEmitterCapability, g as runningFromDishwasherOperation, h as powerMeterCapability, j as literal, k as _enum, l as deviceAdoptionCapability, m as numericSensorCapability, n as BaseDeviceProvider, r as DishwasherOperationStateSchema, s as connectionTestCapability, t as BaseDevice, u as dishwasherCapability, w as createEvent, x as DeviceRole } from "../dist-D9i3id5N.mjs";
2
+ import { a as parseOAuthCallback, i as loginFailureForAttempt, n as cascadeRemoveDevicesForIntegration, r as createSignInAttemptStore, t as setIntegrationDevicesOnline } from "../account-integration-offline-Ciq9CZa-.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,14 @@ var homeConnectDishwasherSchema = object({
4611
4620
  });
4612
4621
  var DISH_CAP = "dishwasher";
4613
4622
  var CONSUMABLES_CAP = "consumables";
4623
+ var RUNNING_CAP = "running-state";
4624
+ var RUNNING_COLD = {
4625
+ running: null,
4626
+ since: null,
4627
+ source: "native",
4628
+ phase: null,
4629
+ lastFetchedAt: 0
4630
+ };
4614
4631
  var CONSUMABLES_EMPTY = {
4615
4632
  items: [],
4616
4633
  lastChangedAt: 0,
@@ -4646,7 +4663,12 @@ var HomeConnectDishwasherDevice = class extends BaseDevice {
4646
4663
  await super.onActivate();
4647
4664
  this.registerDishwasherCap();
4648
4665
  this.registerConsumablesCap();
4666
+ this.registerRunningCap();
4649
4667
  this.runtimeState.setCapState(DISH_CAP, COLD_DISHWASHER_STATUS);
4668
+ this.runtimeState.setCapState(RUNNING_CAP, {
4669
+ ...RUNNING_COLD,
4670
+ lastFetchedAt: Date.now()
4671
+ });
4650
4672
  this.markOnline(false);
4651
4673
  this.#unwatch = homeConnectConnections.watch(this.#brokerId, this.#haId, (event) => this.recomputeSlice(event));
4652
4674
  this.recomputeSlice({ kind: "client" });
@@ -4676,6 +4698,7 @@ var HomeConnectDishwasherDevice = class extends BaseDevice {
4676
4698
  lastValueAt
4677
4699
  });
4678
4700
  this.runtimeState.setCapState(DISH_CAP, slice);
4701
+ this.publishRunningSlice(slice, lastValueAt);
4679
4702
  this.writeConsumables(dishwasherConsumablesFromFeatures(view));
4680
4703
  this.markOnline(slice.availability !== "offline");
4681
4704
  }
@@ -4778,6 +4801,14 @@ var HomeConnectDishwasherDevice = class extends BaseDevice {
4778
4801
  setOption: async ({ key, value }) => this.writeKey(this.requireClient(), key, value)
4779
4802
  });
4780
4803
  }
4804
+ registerRunningCap() {
4805
+ this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? RUNNING_COLD });
4806
+ }
4807
+ publishRunningSlice(slice, sinceMs) {
4808
+ const previous = this.runtimeState.getCapState(RUNNING_CAP);
4809
+ const next = runningFromDishwasherOperation(slice.operationState, slice.program, previous, sinceMs);
4810
+ this.runtimeState.setCapState(RUNNING_CAP, next);
4811
+ }
4781
4812
  };
4782
4813
  //#endregion
4783
4814
  //#region src/home-connect/devices/home-connect-event-device.ts
@@ -5241,21 +5272,16 @@ var BoschHomeConnectProviderAddon = class extends BaseDeviceProvider {
5241
5272
  ],
5242
5273
  customActions: homeConnectActions,
5243
5274
  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
- },
5275
+ beginSignIn: async () => ({
5276
+ ...publishHomeConnectSignIn(homeConnectSignInAttempts, Date.now()),
5277
+ summary: "Open the link in the browser and sign in."
5278
+ }),
5253
5279
  finishSignIn: async (input) => {
5254
- const account = await completeHomeConnectSignIn({
5255
- verifier: input.verifier,
5280
+ const account = await finishHomeConnectSignIn({
5281
+ attemptId: input.attemptId,
5256
5282
  callback: input.callback,
5257
- state: input.state
5258
- });
5283
+ nowMs: Date.now()
5284
+ }, homeConnectSignInAttempts);
5259
5285
  const { rows, secrets } = splitApplianceSecrets(account.appliances.map((app) => ({
5260
5286
  haId: app.haId,
5261
5287
  name: app.name,
@@ -5269,11 +5295,16 @@ var BoschHomeConnectProviderAddon = class extends BaseDeviceProvider {
5269
5295
  return {
5270
5296
  token: account.token,
5271
5297
  ...account.refresh.length > 0 ? { refresh: account.refresh } : {},
5298
+ accountLabel: account.accountId,
5272
5299
  appliances: JSON.stringify(rows),
5273
5300
  applianceSecrets: JSON.stringify(secrets),
5274
5301
  summary: account.summary
5275
5302
  };
5276
- }
5303
+ },
5304
+ signOut: async () => ({
5305
+ signedOut: true,
5306
+ summary: "Signed out."
5307
+ })
5277
5308
  }
5278
5309
  };
5279
5310
  }