@camstack/addon-provider-bosch 0.1.53 → 0.1.55

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_bosch_instances = require("../bosch-instances-dqTG9no9.js");
3
- const require_account_integration_offline = require("../account-integration-offline-C-KJYRdX.js");
2
+ const require_bosch_instances = require("../bosch-instances-DaWS-s0M.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-DzGZkIwF.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");
@@ -1,5 +1,5 @@
1
- import { $ as DeviceType, B as rowsDepsOver, C as plainEventVocabulary, D as runningFromDishwasherOperation, F as defineCustomActions, G as literal, I as defineEntryKind, J as record, L as entryBody, M as RecordIntegrationInstances, N as createEvent, O as runningStateCapability, P as customAction, Q as DeviceRole, R as isSecretMarker, S as numericSensorCapability, T as quantityFromUnit, U as array, V as _enum, X as union, Y as string, Z as unknown, a as BaseDevice, b as ingestReading, c as RUNNING_STATE_COLD, et as errMsg, f as consumablesCapability, g as eventEmitterCapability, h as enumSensorCapability, i as testBoschInstance, m as dishwasherCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as object, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, tt as EventCategory, u as binaryCapability, w as powerMeterCapability, x as integrationInstancesCapability, z as isSecretMarkerLike } from "../bosch-instances-y_A00js_.mjs";
2
- import { a as loginFailureForAttempt, c as HOME_CONNECT_REDIRECT_URI, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, t as setIntegrationDevicesOnline } from "../account-integration-offline-KQcJ_VVK.mjs";
1
+ import { $ as union, A as runningFromDishwasherOperation, B as entryBody, C as ingestReading, D as powerMeterCapability, E as plainEventVocabulary, F as RecordIntegrationInstances, H as isSecretMarkerLike, I as createEvent, J as literal, K as array, L as customAction, O as quantityFromUnit, Q as string, R as defineCustomActions, T as numericSensorCapability, U as rowsDepsOver, V as isSecretMarker, W as _enum, X as object, Z as record, _ as enumSensorCapability, a as BaseDevice, c as RUNNING_STATE_COLD, d as binaryCapability, et as unknown, g as dishwasherCapability, h as deviceAdoptionCapability, i as testBoschInstance, it as EventCategory, j as runningStateCapability, n as boschRowsPorts, nt as DeviceType, o as BaseDeviceProvider, p as consumablesCapability, r as keepBodyKeys, rt as errMsg, s as DishwasherOperationStateSchema, t as boschRowsMigration, tt as DeviceRole, v as eventEmitterCapability, w as integrationInstancesCapability, z as defineEntryKind } from "../bosch-instances-g54r3Dus.mjs";
2
+ import { a as loginFailureForAttempt, c as HOME_CONNECT_REDIRECT_URI, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, t as setIntegrationDevicesOnline } from "../account-integration-offline-xi6muNeM.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { connect } from "node:tls";
5
5
  import { inflateRawSync } from "node:zlib";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-dqTG9no9.js");
3
- const require_account_integration_offline = require("../account-integration-offline-C-KJYRdX.js");
2
+ const require_bosch_instances = require("../bosch-instances-DaWS-s0M.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-DzGZkIwF.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_child_process = require("node:child_process");
6
6
  let node_fs = require("node:fs");
@@ -526,6 +526,32 @@ var homeComActions = require_bosch_instances.defineCustomActions({
526
526
  })
527
527
  });
528
528
  //#endregion
529
+ //#region src/homecom/homecom-session-alert.ts
530
+ /** The standard session-lost alert for HomeCom; "Sign in again" opens the integration page. */
531
+ function createHomeComSessionAlert(deps) {
532
+ const tracker = new require_bosch_instances.SessionHealthTracker({
533
+ kind: "homecom",
534
+ providerName: "Bosch HomeCom",
535
+ logger: deps.logger,
536
+ getAlerts: deps.getAlerts,
537
+ debounceMs: deps.debounceMs ?? 0
538
+ });
539
+ return {
540
+ authFailed: (integrationId) => tracker.lost(integrationId, {
541
+ integrationName: deps.nameOf(integrationId),
542
+ reason: "token-expired",
543
+ action: {
544
+ label: "Sign in again",
545
+ href: `/integrations/${encodeURIComponent(integrationId)}`,
546
+ navigation: "app"
547
+ }
548
+ }),
549
+ connected: (integrationId) => tracker.restored(integrationId),
550
+ removed: (integrationId) => tracker.forget(integrationId),
551
+ dispose: () => tracker.dispose()
552
+ };
553
+ }
554
+ //#endregion
529
555
  //#region src/homecom/homecom-explore-action.ts
530
556
  /** The action refused before it walked anything; `reason` says why. */
531
557
  var HomeComExploreRefusedError = class extends Error {
@@ -1845,6 +1871,14 @@ function homeComResourceUrl(gatewayId, resourcePath) {
1845
1871
  const path = resourcePath.startsWith("/") ? resourcePath : `/${resourcePath}`;
1846
1872
  return `${HOMECOM_POINTT_API_ORIGIN}${HOMECOM_GATEWAYS_PATH}${encodeURIComponent(gatewayId)}/resource${path}`;
1847
1873
  }
1874
+ async function isInvalidGrantBody(res) {
1875
+ try {
1876
+ const body = await res.json();
1877
+ return isRecord$1(body) && body["error"] === "invalid_grant";
1878
+ } catch {
1879
+ return false;
1880
+ }
1881
+ }
1848
1882
  function createHomeComApiClient(config, gatewayId, deps = {}) {
1849
1883
  const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
1850
1884
  const refreshOnUnauthorized = deps.refreshOnUnauthorized ?? true;
@@ -1965,6 +1999,7 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1965
1999
  });
1966
2000
  if (!res.ok) {
1967
2001
  if (res.status === 401 || res.status === 403) throw new HomeComAuthError("refresh refused");
2002
+ if (res.status === 400 && await isInvalidGrantBody(res)) throw new HomeComAuthError("refresh refused");
1968
2003
  throw new Error(`HomeCom token refresh failed: HTTP ${res.status}`);
1969
2004
  }
1970
2005
  const body = await res.json();
@@ -2384,7 +2419,10 @@ function buildSerialPatcher(deps) {
2384
2419
  const run = async () => {
2385
2420
  try {
2386
2421
  const stored = await deps.read(integrationId);
2387
- if (stored === null) return;
2422
+ if (stored === null) {
2423
+ deps.logger.warn(`HomeCom not persisting ${deps.what} — the integration record is gone`, { tags: { integrationId } });
2424
+ return;
2425
+ }
2388
2426
  if (stored.locked) {
2389
2427
  deps.logger.warn(`HomeCom not persisting ${deps.what} — stored secrets are locked`, { tags: { integrationId } });
2390
2428
  return;
@@ -2471,7 +2509,7 @@ var HOMECOM_GATEWAY_NATIVE_ID = "gateway:0";
2471
2509
  function gatewayDiscoverable(snapshot) {
2472
2510
  return {
2473
2511
  nativeId: HOMECOM_GATEWAY_NATIVE_ID,
2474
- name: snapshot.gateway.name.length > 0 ? snapshot.gateway.name : "HomeCom gateway",
2512
+ name: require_bosch_instances.gatewayDeviceName("Bosch HomeCom", snapshot.deviceType),
2475
2513
  kind: "gateway"
2476
2514
  };
2477
2515
  }
@@ -2798,6 +2836,7 @@ var HomeComIntegrationManager = class {
2798
2836
  #makeClient;
2799
2837
  #pollIntervalMs;
2800
2838
  #onGatewayChosen;
2839
+ #onAuthFailed;
2801
2840
  #gateways = [];
2802
2841
  #client = null;
2803
2842
  #gatewayId = null;
@@ -2816,6 +2855,7 @@ var HomeComIntegrationManager = class {
2816
2855
  const fetchFn = options.fetchFn;
2817
2856
  const bridge = options.bridge;
2818
2857
  this.#onGatewayChosen = options.onGatewayChosen ?? (() => void 0);
2858
+ this.#onAuthFailed = options.onAuthFailed ?? (() => void 0);
2819
2859
  const onTokensRotated = async (tokens) => {
2820
2860
  this.#connection = {
2821
2861
  ...this.#connection,
@@ -2884,7 +2924,9 @@ var HomeComIntegrationManager = class {
2884
2924
  }, this.#pollIntervalMs);
2885
2925
  } catch (err) {
2886
2926
  this.#status = "error";
2887
- this.#error = err instanceof HomeComAuthError ? "authentication failed — sign in again" : require_bosch_instances.errMsg(err);
2927
+ const authFailed = err instanceof HomeComAuthError;
2928
+ this.#error = authFailed ? "authentication failed — sign in again" : require_bosch_instances.errMsg(err);
2929
+ if (authFailed) this.#onAuthFailed(this.#id);
2888
2930
  this.#client = null;
2889
2931
  this.#surfaceSink.set(this.#id, null);
2890
2932
  this.#onDisconnected(this.#id);
@@ -2916,6 +2958,7 @@ var HomeComIntegrationManager = class {
2916
2958
  this.#surfaceSink.updateSnapshot(this.#id, snapshot);
2917
2959
  this.#lastCheckedAt = Date.now();
2918
2960
  } catch (err) {
2961
+ if (err instanceof HomeComAuthError) this.#onAuthFailed(this.#id);
2919
2962
  this.#logger.warn("HomeCom poll failed", { meta: {
2920
2963
  integrationId: this.#id,
2921
2964
  error: require_bosch_instances.errMsg(err)
@@ -2950,6 +2993,7 @@ var HomeComClientRegistry = class {
2950
2993
  #makeManager;
2951
2994
  #onTokensRotated;
2952
2995
  #onGatewayChosen;
2996
+ #onAuthFailed;
2953
2997
  #entries = /* @__PURE__ */ new Map();
2954
2998
  constructor(logger, deps = {}) {
2955
2999
  this.#logger = logger;
@@ -2957,6 +3001,7 @@ var HomeComClientRegistry = class {
2957
3001
  this.#onDisconnected = deps.onDisconnected ?? (() => void 0);
2958
3002
  this.#onTokensRotated = deps.onTokensRotated ?? (async () => void 0);
2959
3003
  this.#onGatewayChosen = deps.onGatewayChosen ?? (() => void 0);
3004
+ this.#onAuthFailed = deps.onAuthFailed ?? (() => void 0);
2960
3005
  const fetchFn = deps.fetchFn;
2961
3006
  const makeClient = deps.makeClient;
2962
3007
  const bridge = deps.bridge;
@@ -2969,6 +3014,7 @@ var HomeComClientRegistry = class {
2969
3014
  onDisconnected: opts.onDisconnected,
2970
3015
  onTokensRotated: opts.onTokensRotated,
2971
3016
  onGatewayChosen: opts.onGatewayChosen,
3017
+ onAuthFailed: opts.onAuthFailed,
2972
3018
  surfaceSink: homeComConnections,
2973
3019
  fetchFn,
2974
3020
  makeClient,
@@ -3008,7 +3054,8 @@ var HomeComClientRegistry = class {
3008
3054
  onConnected: (id) => this.#onConnected(id),
3009
3055
  onDisconnected: (id) => this.#onDisconnected(id),
3010
3056
  onTokensRotated: (tokens) => this.#onTokensRotated(integrationId, tokens),
3011
- onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId)
3057
+ onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId),
3058
+ onAuthFailed: (id) => this.#onAuthFailed(id)
3012
3059
  });
3013
3060
  this.#entries.set(integrationId, {
3014
3061
  manager,
@@ -3660,6 +3707,8 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3660
3707
  };
3661
3708
  clients = null;
3662
3709
  instances = null;
3710
+ sessionAlert = null;
3711
+ integrationNames = /* @__PURE__ */ new Map();
3663
3712
  constructor() {
3664
3713
  super({});
3665
3714
  }
@@ -3686,12 +3735,20 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3686
3735
  const persistTokens = buildHomeComTokenPersister(persistDeps);
3687
3736
  const persistGateway = buildHomeComGatewayPersister(persistDeps);
3688
3737
  const bridge = await this.createBridge();
3738
+ const sessionAlert = createHomeComSessionAlert({
3739
+ logger: this.ctx.logger,
3740
+ getAlerts: () => this.ctx.api.alerts,
3741
+ nameOf: (id) => this.integrationNames.get(id) ?? id
3742
+ });
3743
+ this.sessionAlert = sessionAlert;
3689
3744
  this.clients = new HomeComClientRegistry(this.ctx.logger, {
3745
+ onAuthFailed: (integrationId) => sessionAlert.authFailed(integrationId),
3690
3746
  ...bridge !== null ? { bridge } : {},
3691
3747
  onTokensRotated: (integrationId, tokens) => persistTokens(integrationId, tokens),
3692
3748
  onGatewayChosen: (integrationId, gatewayId) => void persistGateway(integrationId, gatewayId)
3693
3749
  });
3694
3750
  this.clients.setOnConnected((integrationId) => {
3751
+ sessionAlert.connected(integrationId);
3695
3752
  this.setIntegrationDevicesOnline(integrationId, true);
3696
3753
  });
3697
3754
  this.clients.setOnDisconnected((integrationId) => {
@@ -3743,6 +3800,8 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3743
3800
  }
3744
3801
  this.clients = null;
3745
3802
  this.instances = null;
3803
+ this.sessionAlert?.dispose();
3804
+ this.sessionAlert = null;
3746
3805
  await super.onShutdown();
3747
3806
  }
3748
3807
  /** The `homecom_alt` bridge on the embedded Python; null leaves k40/k30/icom on plain reads. */
@@ -3781,6 +3840,7 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3781
3840
  for (const integration of mine) try {
3782
3841
  const connection = await homeComConnectionOf(this.requireInstances().instances, integration.id);
3783
3842
  if (!connection) continue;
3843
+ this.integrationNames.set(integration.id, integration.name);
3784
3844
  await reg.upsert(integration.id, integration.name, connection);
3785
3845
  surviving.add(integration.id);
3786
3846
  } catch (err) {
@@ -3791,6 +3851,10 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3791
3851
  }
3792
3852
  const vanished = reg.list().filter((id) => !surviving.has(id));
3793
3853
  await reg.retain(surviving);
3854
+ for (const integrationId of vanished) {
3855
+ this.integrationNames.delete(integrationId);
3856
+ this.sessionAlert?.removed(integrationId);
3857
+ }
3794
3858
  const deviceReg = this.ctx.kernel.deviceRegistry;
3795
3859
  const deviceApi = this.ctx.kernel.devices;
3796
3860
  if (deviceReg && deviceApi) for (const integrationId of vanished) await require_account_integration_offline.cascadeRemoveDevicesForIntegration({
@@ -1,5 +1,5 @@
1
- import { $ as DeviceType, B as rowsDepsOver, E as registerPowerSwitch, F as defineCustomActions, G as literal, H as _null, I as defineEntryKind, J as record, K as number, L as entryBody, M as RecordIntegrationInstances, P as customAction, Q as DeviceRole, U as array, V as _enum, W as boolean, X as union, Y as string, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, et as errMsg, i as testBoschInstance, j as waterHeaterCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as object, r as keepBodyKeys, t as boschRowsMigration, tt as EventCategory, v as heatPumpStatusCapability, x as integrationInstancesCapability } from "../bosch-instances-y_A00js_.mjs";
2
- import { a as loginFailureForAttempt, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, s as HOMECOM_REDIRECT_URI, t as setIntegrationDevicesOnline } from "../account-integration-offline-KQcJ_VVK.mjs";
1
+ import { $ as union, B as entryBody, F as RecordIntegrationInstances, G as _null, J as literal, K as array, L as customAction, P as waterHeaterCapability, Q as string, R as defineCustomActions, U as rowsDepsOver, W as _enum, X as object, Y as number, Z as record, a as BaseDevice, b as heatPumpControlCapability, f as climateControlCapability, h as deviceAdoptionCapability, i as testBoschInstance, it as EventCategory, k as registerPowerSwitch, l as SessionHealthTracker, n as boschRowsPorts, nt as DeviceType, o as BaseDeviceProvider, q as boolean, r as keepBodyKeys, rt as errMsg, t as boschRowsMigration, tt as DeviceRole, w as integrationInstancesCapability, x as heatPumpStatusCapability, y as gatewayDeviceName, z as defineEntryKind } from "../bosch-instances-g54r3Dus.mjs";
2
+ import { a as loginFailureForAttempt, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, s as HOMECOM_REDIRECT_URI, t as setIntegrationDevicesOnline } from "../account-integration-offline-xi6muNeM.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { spawn } from "node:child_process";
5
5
  import { existsSync } from "node:fs";
@@ -525,6 +525,32 @@ var homeComActions = defineCustomActions({
525
525
  })
526
526
  });
527
527
  //#endregion
528
+ //#region src/homecom/homecom-session-alert.ts
529
+ /** The standard session-lost alert for HomeCom; "Sign in again" opens the integration page. */
530
+ function createHomeComSessionAlert(deps) {
531
+ const tracker = new SessionHealthTracker({
532
+ kind: "homecom",
533
+ providerName: "Bosch HomeCom",
534
+ logger: deps.logger,
535
+ getAlerts: deps.getAlerts,
536
+ debounceMs: deps.debounceMs ?? 0
537
+ });
538
+ return {
539
+ authFailed: (integrationId) => tracker.lost(integrationId, {
540
+ integrationName: deps.nameOf(integrationId),
541
+ reason: "token-expired",
542
+ action: {
543
+ label: "Sign in again",
544
+ href: `/integrations/${encodeURIComponent(integrationId)}`,
545
+ navigation: "app"
546
+ }
547
+ }),
548
+ connected: (integrationId) => tracker.restored(integrationId),
549
+ removed: (integrationId) => tracker.forget(integrationId),
550
+ dispose: () => tracker.dispose()
551
+ };
552
+ }
553
+ //#endregion
528
554
  //#region src/homecom/homecom-explore-action.ts
529
555
  /** The action refused before it walked anything; `reason` says why. */
530
556
  var HomeComExploreRefusedError = class extends Error {
@@ -1844,6 +1870,14 @@ function homeComResourceUrl(gatewayId, resourcePath) {
1844
1870
  const path = resourcePath.startsWith("/") ? resourcePath : `/${resourcePath}`;
1845
1871
  return `${HOMECOM_POINTT_API_ORIGIN}${HOMECOM_GATEWAYS_PATH}${encodeURIComponent(gatewayId)}/resource${path}`;
1846
1872
  }
1873
+ async function isInvalidGrantBody(res) {
1874
+ try {
1875
+ const body = await res.json();
1876
+ return isRecord$1(body) && body["error"] === "invalid_grant";
1877
+ } catch {
1878
+ return false;
1879
+ }
1880
+ }
1847
1881
  function createHomeComApiClient(config, gatewayId, deps = {}) {
1848
1882
  const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
1849
1883
  const refreshOnUnauthorized = deps.refreshOnUnauthorized ?? true;
@@ -1964,6 +1998,7 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1964
1998
  });
1965
1999
  if (!res.ok) {
1966
2000
  if (res.status === 401 || res.status === 403) throw new HomeComAuthError("refresh refused");
2001
+ if (res.status === 400 && await isInvalidGrantBody(res)) throw new HomeComAuthError("refresh refused");
1967
2002
  throw new Error(`HomeCom token refresh failed: HTTP ${res.status}`);
1968
2003
  }
1969
2004
  const body = await res.json();
@@ -2383,7 +2418,10 @@ function buildSerialPatcher(deps) {
2383
2418
  const run = async () => {
2384
2419
  try {
2385
2420
  const stored = await deps.read(integrationId);
2386
- if (stored === null) return;
2421
+ if (stored === null) {
2422
+ deps.logger.warn(`HomeCom not persisting ${deps.what} — the integration record is gone`, { tags: { integrationId } });
2423
+ return;
2424
+ }
2387
2425
  if (stored.locked) {
2388
2426
  deps.logger.warn(`HomeCom not persisting ${deps.what} — stored secrets are locked`, { tags: { integrationId } });
2389
2427
  return;
@@ -2470,7 +2508,7 @@ var HOMECOM_GATEWAY_NATIVE_ID = "gateway:0";
2470
2508
  function gatewayDiscoverable(snapshot) {
2471
2509
  return {
2472
2510
  nativeId: HOMECOM_GATEWAY_NATIVE_ID,
2473
- name: snapshot.gateway.name.length > 0 ? snapshot.gateway.name : "HomeCom gateway",
2511
+ name: gatewayDeviceName("Bosch HomeCom", snapshot.deviceType),
2474
2512
  kind: "gateway"
2475
2513
  };
2476
2514
  }
@@ -2797,6 +2835,7 @@ var HomeComIntegrationManager = class {
2797
2835
  #makeClient;
2798
2836
  #pollIntervalMs;
2799
2837
  #onGatewayChosen;
2838
+ #onAuthFailed;
2800
2839
  #gateways = [];
2801
2840
  #client = null;
2802
2841
  #gatewayId = null;
@@ -2815,6 +2854,7 @@ var HomeComIntegrationManager = class {
2815
2854
  const fetchFn = options.fetchFn;
2816
2855
  const bridge = options.bridge;
2817
2856
  this.#onGatewayChosen = options.onGatewayChosen ?? (() => void 0);
2857
+ this.#onAuthFailed = options.onAuthFailed ?? (() => void 0);
2818
2858
  const onTokensRotated = async (tokens) => {
2819
2859
  this.#connection = {
2820
2860
  ...this.#connection,
@@ -2883,7 +2923,9 @@ var HomeComIntegrationManager = class {
2883
2923
  }, this.#pollIntervalMs);
2884
2924
  } catch (err) {
2885
2925
  this.#status = "error";
2886
- this.#error = err instanceof HomeComAuthError ? "authentication failed — sign in again" : errMsg(err);
2926
+ const authFailed = err instanceof HomeComAuthError;
2927
+ this.#error = authFailed ? "authentication failed — sign in again" : errMsg(err);
2928
+ if (authFailed) this.#onAuthFailed(this.#id);
2887
2929
  this.#client = null;
2888
2930
  this.#surfaceSink.set(this.#id, null);
2889
2931
  this.#onDisconnected(this.#id);
@@ -2915,6 +2957,7 @@ var HomeComIntegrationManager = class {
2915
2957
  this.#surfaceSink.updateSnapshot(this.#id, snapshot);
2916
2958
  this.#lastCheckedAt = Date.now();
2917
2959
  } catch (err) {
2960
+ if (err instanceof HomeComAuthError) this.#onAuthFailed(this.#id);
2918
2961
  this.#logger.warn("HomeCom poll failed", { meta: {
2919
2962
  integrationId: this.#id,
2920
2963
  error: errMsg(err)
@@ -2949,6 +2992,7 @@ var HomeComClientRegistry = class {
2949
2992
  #makeManager;
2950
2993
  #onTokensRotated;
2951
2994
  #onGatewayChosen;
2995
+ #onAuthFailed;
2952
2996
  #entries = /* @__PURE__ */ new Map();
2953
2997
  constructor(logger, deps = {}) {
2954
2998
  this.#logger = logger;
@@ -2956,6 +3000,7 @@ var HomeComClientRegistry = class {
2956
3000
  this.#onDisconnected = deps.onDisconnected ?? (() => void 0);
2957
3001
  this.#onTokensRotated = deps.onTokensRotated ?? (async () => void 0);
2958
3002
  this.#onGatewayChosen = deps.onGatewayChosen ?? (() => void 0);
3003
+ this.#onAuthFailed = deps.onAuthFailed ?? (() => void 0);
2959
3004
  const fetchFn = deps.fetchFn;
2960
3005
  const makeClient = deps.makeClient;
2961
3006
  const bridge = deps.bridge;
@@ -2968,6 +3013,7 @@ var HomeComClientRegistry = class {
2968
3013
  onDisconnected: opts.onDisconnected,
2969
3014
  onTokensRotated: opts.onTokensRotated,
2970
3015
  onGatewayChosen: opts.onGatewayChosen,
3016
+ onAuthFailed: opts.onAuthFailed,
2971
3017
  surfaceSink: homeComConnections,
2972
3018
  fetchFn,
2973
3019
  makeClient,
@@ -3007,7 +3053,8 @@ var HomeComClientRegistry = class {
3007
3053
  onConnected: (id) => this.#onConnected(id),
3008
3054
  onDisconnected: (id) => this.#onDisconnected(id),
3009
3055
  onTokensRotated: (tokens) => this.#onTokensRotated(integrationId, tokens),
3010
- onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId)
3056
+ onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId),
3057
+ onAuthFailed: (id) => this.#onAuthFailed(id)
3011
3058
  });
3012
3059
  this.#entries.set(integrationId, {
3013
3060
  manager,
@@ -3659,6 +3706,8 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3659
3706
  };
3660
3707
  clients = null;
3661
3708
  instances = null;
3709
+ sessionAlert = null;
3710
+ integrationNames = /* @__PURE__ */ new Map();
3662
3711
  constructor() {
3663
3712
  super({});
3664
3713
  }
@@ -3685,12 +3734,20 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3685
3734
  const persistTokens = buildHomeComTokenPersister(persistDeps);
3686
3735
  const persistGateway = buildHomeComGatewayPersister(persistDeps);
3687
3736
  const bridge = await this.createBridge();
3737
+ const sessionAlert = createHomeComSessionAlert({
3738
+ logger: this.ctx.logger,
3739
+ getAlerts: () => this.ctx.api.alerts,
3740
+ nameOf: (id) => this.integrationNames.get(id) ?? id
3741
+ });
3742
+ this.sessionAlert = sessionAlert;
3688
3743
  this.clients = new HomeComClientRegistry(this.ctx.logger, {
3744
+ onAuthFailed: (integrationId) => sessionAlert.authFailed(integrationId),
3689
3745
  ...bridge !== null ? { bridge } : {},
3690
3746
  onTokensRotated: (integrationId, tokens) => persistTokens(integrationId, tokens),
3691
3747
  onGatewayChosen: (integrationId, gatewayId) => void persistGateway(integrationId, gatewayId)
3692
3748
  });
3693
3749
  this.clients.setOnConnected((integrationId) => {
3750
+ sessionAlert.connected(integrationId);
3694
3751
  this.setIntegrationDevicesOnline(integrationId, true);
3695
3752
  });
3696
3753
  this.clients.setOnDisconnected((integrationId) => {
@@ -3742,6 +3799,8 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3742
3799
  }
3743
3800
  this.clients = null;
3744
3801
  this.instances = null;
3802
+ this.sessionAlert?.dispose();
3803
+ this.sessionAlert = null;
3745
3804
  await super.onShutdown();
3746
3805
  }
3747
3806
  /** The `homecom_alt` bridge on the embedded Python; null leaves k40/k30/icom on plain reads. */
@@ -3780,6 +3839,7 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3780
3839
  for (const integration of mine) try {
3781
3840
  const connection = await homeComConnectionOf(this.requireInstances().instances, integration.id);
3782
3841
  if (!connection) continue;
3842
+ this.integrationNames.set(integration.id, integration.name);
3783
3843
  await reg.upsert(integration.id, integration.name, connection);
3784
3844
  surviving.add(integration.id);
3785
3845
  } catch (err) {
@@ -3790,6 +3850,10 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3790
3850
  }
3791
3851
  const vanished = reg.list().filter((id) => !surviving.has(id));
3792
3852
  await reg.retain(surviving);
3853
+ for (const integrationId of vanished) {
3854
+ this.integrationNames.delete(integrationId);
3855
+ this.sessionAlert?.removed(integrationId);
3856
+ }
3793
3857
  const deviceReg = this.ctx.kernel.deviceRegistry;
3794
3858
  const deviceApi = this.ctx.kernel.devices;
3795
3859
  if (deviceReg && deviceApi) for (const integrationId of vanished) await cascadeRemoveDevicesForIntegration({
package/dist/shc/addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
- const require_bosch_instances = require("../bosch-instances-dqTG9no9.js");
5
+ const require_bosch_instances = require("../bosch-instances-DaWS-s0M.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_https = require("node:https");
8
8
  let node_child_process = require("node:child_process");
@@ -638,7 +638,7 @@ function shcDiscoveryCandidates(proposals) {
638
638
  return proposals.map((found) => ({
639
639
  stableId: `bosch-shc:${found.id}`,
640
640
  type: require_bosch_instances.DeviceType.Gateway,
641
- suggestedName: found.name,
641
+ suggestedName: require_bosch_instances.defaultIntegrationName("Bosch SHC"),
642
642
  prefilledConfig: { host: found.host },
643
643
  setupIntegration: true
644
644
  }));
@@ -895,7 +895,7 @@ function buildShcCandidates(input) {
895
895
  const gatewayAdopted = input.adopted.get(gatewayId) ?? null;
896
896
  const out = [{
897
897
  childNativeId: gatewayId,
898
- name: input.integrationName,
898
+ name: require_bosch_instances.gatewayDeviceName("Bosch", "SHC"),
899
899
  type: require_bosch_instances.DeviceType.Gateway,
900
900
  status: "online",
901
901
  metadata: {
@@ -1376,7 +1376,7 @@ function buildShcAdoptionProvider(deps) {
1376
1376
  let failures = 0;
1377
1377
  for (const shcDeviceId of childNativeIds) try {
1378
1378
  if (shcDeviceId === shcGatewayNativeId(integrationId)) {
1379
- const name = perCandidate?.[shcDeviceId]?.name ?? integrationName(integrationId) ?? "Bosch SHC";
1379
+ const name = perCandidate?.[shcDeviceId]?.name ?? require_bosch_instances.gatewayDeviceName("Bosch", "SHC");
1380
1380
  const { deviceId } = await adoptDevice({
1381
1381
  shcDeviceId,
1382
1382
  integrationId,
@@ -1,4 +1,4 @@
1
- import { $ as DeviceType, A as temperatureSensorCapability, B as rowsDepsOver, E as registerPowerSwitch, F as defineCustomActions, G as literal, I as defineEntryKind, K as number, L as entryBody, M as RecordIntegrationInstances, P as customAction, Q as DeviceRole, U as array, V as _enum, Y as string, a as BaseDevice, d as climateControlCapability, et as errMsg, i as testBoschInstance, k as switchCapability, l as batteryCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as object, r as keepBodyKeys, t as boschRowsMigration, tt as EventCategory, x as integrationInstancesCapability, y as heatingValveCapability } from "../bosch-instances-y_A00js_.mjs";
1
+ import { B as entryBody, F as RecordIntegrationInstances, J as literal, K as array, L as customAction, M as switchCapability, N as temperatureSensorCapability, Q as string, R as defineCustomActions, S as heatingValveCapability, U as rowsDepsOver, W as _enum, X as object, Y as number, a as BaseDevice, f as climateControlCapability, h as deviceAdoptionCapability, i as testBoschInstance, it as EventCategory, k as registerPowerSwitch, m as defaultIntegrationName, n as boschRowsPorts, nt as DeviceType, o as BaseDeviceProvider, r as keepBodyKeys, rt as errMsg, t as boschRowsMigration, tt as DeviceRole, u as batteryCapability, w as integrationInstancesCapability, y as gatewayDeviceName, z as defineEntryKind } from "../bosch-instances-g54r3Dus.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
@@ -639,7 +639,7 @@ function shcDiscoveryCandidates(proposals) {
639
639
  return proposals.map((found) => ({
640
640
  stableId: `bosch-shc:${found.id}`,
641
641
  type: DeviceType.Gateway,
642
- suggestedName: found.name,
642
+ suggestedName: defaultIntegrationName("Bosch SHC"),
643
643
  prefilledConfig: { host: found.host },
644
644
  setupIntegration: true
645
645
  }));
@@ -896,7 +896,7 @@ function buildShcCandidates(input) {
896
896
  const gatewayAdopted = input.adopted.get(gatewayId) ?? null;
897
897
  const out = [{
898
898
  childNativeId: gatewayId,
899
- name: input.integrationName,
899
+ name: gatewayDeviceName("Bosch", "SHC"),
900
900
  type: DeviceType.Gateway,
901
901
  status: "online",
902
902
  metadata: {
@@ -1377,7 +1377,7 @@ function buildShcAdoptionProvider(deps) {
1377
1377
  let failures = 0;
1378
1378
  for (const shcDeviceId of childNativeIds) try {
1379
1379
  if (shcDeviceId === shcGatewayNativeId(integrationId)) {
1380
- const name = perCandidate?.[shcDeviceId]?.name ?? integrationName(integrationId) ?? "Bosch SHC";
1380
+ const name = perCandidate?.[shcDeviceId]?.name ?? gatewayDeviceName("Bosch", "SHC");
1381
1381
  const { deviceId } = await adoptDevice({
1382
1382
  shcDeviceId,
1383
1383
  integrationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "Bosch smart-home device-provider addons for CamStack — SHC, Home Connect, and HomeCom cloud integrations (scaffold)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -39,6 +39,7 @@
39
39
  "integrationRole": "devices",
40
40
  "category": "providers",
41
41
  "name": "Bosch Smart Home",
42
+ "providerLabel": "Bosch SHC",
42
43
  "version": "0.1.0",
43
44
  "description": "Adopt Bosch Smart Home Controller (SHC) devices via account onboarding — scaffold",
44
45
  "entry": "./dist/shc/addon.js",
@@ -141,7 +142,7 @@
141
142
  }
142
143
  ],
143
144
  "requires": {
144
- "server": ">=1.2.842"
145
+ "server": ">=1.2.879"
145
146
  }
146
147
  },
147
148
  "files": [