@camstack/addon-provider-bosch 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.38] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - Bosch sign-in through the hub: proxy the SingleKey ID script host so the login page shows its password step instead of only the email field
15
+
10
16
  ## [0.1.37] - 2026-11-05
11
17
 
12
18
  ### Changed
@@ -1,4 +1,4 @@
1
- const require_bosch_instances = require("./bosch-instances-CO8yIFzJ.js");
1
+ const require_bosch_instances = require("./bosch-instances-Y9sMCElY.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/shared/oauth-callback.ts
4
4
  /** A code on its own: no scheme, no query, no separators. */
@@ -1,4 +1,4 @@
1
- import { $ as errMsg } from "./bosch-instances-BQiyzlPt.mjs";
1
+ import { $ as errMsg } from "./bosch-instances-CIuDwXQ8.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  //#region src/shared/oauth-callback.ts
4
4
  /** A code on its own: no scheme, no query, no separators. */
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
5981
5981
  var ZodFirstPartyTypeKind;
5982
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5983
5983
  //#endregion
5984
- //#region ../types/dist/sleep-9TIHFj1w.mjs
5984
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
5985
5985
  /**
5986
5986
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5987
5987
  * window to float samples (D455).
@@ -6790,6 +6790,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6790
6790
  }
6791
6791
  return out;
6792
6792
  }
6793
+ /** Every scope, in the order the guard and the docs list them. */
6794
+ var SCOPED_SETTINGS_SCOPES = [
6795
+ "cluster",
6796
+ "device-defaults",
6797
+ "node"
6798
+ ];
6793
6799
  /**
6794
6800
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6795
6801
  *
@@ -7681,6 +7687,13 @@ var BaseAddon = class {
7681
7687
  */
7682
7688
  secretStatePaths = [];
7683
7689
  _addonSecretTree = null;
7690
+ /**
7691
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7692
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7693
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7694
+ */
7695
+ _scopedByCap = /* @__PURE__ */ new Map();
7696
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7684
7697
  _deviceSecretTree = null;
7685
7698
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7686
7699
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7750,6 +7763,7 @@ var BaseAddon = class {
7750
7763
  this._ctx = context;
7751
7764
  this._secretSettings = null;
7752
7765
  }
7766
+ await this.loadScopedSchemas();
7753
7767
  await this.resolveConfig();
7754
7768
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7755
7769
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7759,6 +7773,7 @@ var BaseAddon = class {
7759
7773
  const normalized = normalizeAddonInitResult(result);
7760
7774
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7761
7775
  this._registeredCapNames = providers.map((p) => p.capability.name);
7776
+ await this.loadScopedSchemas();
7762
7777
  return normalized;
7763
7778
  }
7764
7779
  /**
@@ -7847,6 +7862,16 @@ var BaseAddon = class {
7847
7862
  schema(schema) {
7848
7863
  return schema;
7849
7864
  }
7865
+ /**
7866
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7867
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7868
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7869
+ * must already know which keys are per node. Names the providers register at init are added
7870
+ * once known.
7871
+ */
7872
+ scopedSettingsCapabilities() {
7873
+ return [];
7874
+ }
7850
7875
  /** Override to provide global-level settings UI schema. */
7851
7876
  globalSettingsSchema(_cap) {
7852
7877
  return null;
@@ -7890,7 +7915,8 @@ var BaseAddon = class {
7890
7915
  return [];
7891
7916
  }
7892
7917
  async getGlobalSettings(overlay, cap, nodeId) {
7893
- const schema = this.globalSettingsSchema(cap);
7918
+ await this.loadScopedSchemas(cap);
7919
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7894
7920
  if (!schema) return { sections: [] };
7895
7921
  const projected = await this.readRawProjectedStore(nodeId, cap);
7896
7922
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -8021,6 +8047,7 @@ var BaseAddon = class {
8021
8047
  * resolved blob back is a no-op for every unchanged secret.
8022
8048
  */
8023
8049
  async resolveGlobalStore(nodeId, cap) {
8050
+ await this.loadScopedSchemas(cap);
8024
8051
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8025
8052
  return this.projectForNode(raw, nodeId, cap);
8026
8053
  }
@@ -8031,6 +8058,7 @@ var BaseAddon = class {
8031
8058
  }
8032
8059
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8033
8060
  async readRawProjectedStore(nodeId, cap) {
8061
+ await this.loadScopedSchemas(cap);
8034
8062
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8035
8063
  return this.projectForNode(raw, nodeId, cap);
8036
8064
  }
@@ -8133,6 +8161,7 @@ var BaseAddon = class {
8133
8161
  await this.applyGlobalPatch(patch, nodeId);
8134
8162
  }
8135
8163
  async applyGlobalPatch(barePatch, nodeId) {
8164
+ await this.loadScopedSchemas();
8136
8165
  const keys = this.perNodeKeys();
8137
8166
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8138
8167
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8146,7 +8175,8 @@ var BaseAddon = class {
8146
8175
  await this.resolveConfig();
8147
8176
  await this.onConfigChanged();
8148
8177
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8149
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8178
+ const legacy = this.globalSettingsSchema();
8179
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8150
8180
  }
8151
8181
  /**
8152
8182
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8154,14 +8184,67 @@ var BaseAddon = class {
8154
8184
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8155
8185
  * settings API behaves exactly like the legacy node-agnostic one.
8156
8186
  */
8157
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8187
+ /**
8188
+ * Load the scoped schemas of every capability this addon declares (the override and the
8189
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8190
+ * derived from the previous set. Idempotent.
8191
+ */
8192
+ async loadScopedSchemas(extraCap) {
8193
+ const caps = new Set([
8194
+ ...this.scopedSettingsCapabilities(),
8195
+ ...this._registeredCapNames,
8196
+ ...extraCap === void 0 ? [] : [extraCap]
8197
+ ]);
8198
+ let added = false;
8199
+ for (const cap of caps) {
8200
+ if (this._scopedByCap.has(cap)) continue;
8201
+ const loaded = [];
8202
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8203
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8204
+ if (schema !== null) loaded.push({
8205
+ scope,
8206
+ schema
8207
+ });
8208
+ }
8209
+ this._scopedByCap.set(cap, loaded);
8210
+ added = true;
8211
+ }
8212
+ if (!added) return;
8213
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8214
+ this._addonSecretTree = null;
8215
+ }
8216
+ /**
8217
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8218
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8219
+ */
8220
+ scopedFormSchema(cap) {
8221
+ const byId = /* @__PURE__ */ new Map();
8222
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8223
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8224
+ }
8225
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8226
+ loadedScopedSchemas(cap) {
8227
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8228
+ return [...this._scopedByCap.values()].flat();
8229
+ }
8230
+ /**
8231
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8232
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8233
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8234
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8235
+ * Call {@link loadScopedSchemas} first (the async seams do).
8236
+ */
8158
8237
  perNodeKeys(cap) {
8159
8238
  const cacheKey = cap ?? "";
8160
- const cached = this._perNodeKeysCache.get(cacheKey);
8239
+ const cached = this._nodeKeysByCap.get(cacheKey);
8161
8240
  if (cached) return cached;
8162
- const schema = this.globalSettingsSchema(cap);
8163
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8164
- this._perNodeKeysCache.set(cacheKey, keys);
8241
+ const legacy = this.globalSettingsSchema(cap);
8242
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8243
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8244
+ if (scope !== "node") continue;
8245
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8246
+ }
8247
+ this._nodeKeysByCap.set(cacheKey, keys);
8165
8248
  return keys;
8166
8249
  }
8167
8250
  /**
@@ -8171,10 +8254,9 @@ var BaseAddon = class {
8171
8254
  * time to return its response before the addon is torn down and
8172
8255
  * re-initialised by `AddonRegistryService.restartAddon`.
8173
8256
  */
8174
- maybeAutoRestart(patchKeys, schema) {
8175
- if (!schema) return;
8257
+ maybeAutoRestart(patchKeys, schemas) {
8176
8258
  const restartKeys = /* @__PURE__ */ new Set();
8177
- for (const section of schema.sections) for (const field of section.fields) {
8259
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8178
8260
  if (field.type === "separator" || field.type === "info") continue;
8179
8261
  if (field.requiresRestart) restartKeys.add(field.key);
8180
8262
  }
@@ -8370,12 +8452,14 @@ var BaseAddon = class {
8370
8452
  * here, in the addon's own process, so the hub keeps no copy of it.
8371
8453
  */
8372
8454
  async getDeclaredSettingsKeys() {
8455
+ await this.loadScopedSchemas();
8373
8456
  return { keys: [...this.declaredKeySet()].toSorted() };
8374
8457
  }
8375
8458
  declaredKeySet() {
8376
8459
  const keys = new Set(Object.keys(this.defaults));
8377
8460
  const schema = this.globalSettingsSchema();
8378
8461
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8462
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8379
8463
  for (const key of this._declaredStateKeys) keys.add(key);
8380
8464
  for (const path of this.secretStatePaths) {
8381
8465
  const root = path.split(/[.[]/, 1)[0];
@@ -8471,7 +8555,11 @@ var BaseAddon = class {
8471
8555
  }
8472
8556
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8473
8557
  addonSecretTree() {
8474
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8558
+ if (this._addonSecretTree === null) {
8559
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8560
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8561
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8562
+ }
8475
8563
  return this._addonSecretTree;
8476
8564
  }
8477
8565
  deviceSecretTree() {
@@ -8721,6 +8809,23 @@ function dropPerNodeFields(fields) {
8721
8809
  }
8722
8810
  return kept;
8723
8811
  }
8812
+ /** The keys of the value-holding fields, recursing into layout containers. */
8813
+ function collectValueFieldKeys(fields) {
8814
+ const collected = [];
8815
+ for (const field of fields) {
8816
+ if (field.type === "group") {
8817
+ collected.push(...collectValueFieldKeys(field.fields));
8818
+ continue;
8819
+ }
8820
+ if (field.type === "sub-tabs") {
8821
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8822
+ continue;
8823
+ }
8824
+ if (field.type === "separator" || field.type === "info") continue;
8825
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8826
+ }
8827
+ return collected;
8828
+ }
8724
8829
  function collectFieldKeys(fields) {
8725
8830
  const collected = [];
8726
8831
  for (const field of fields) {
@@ -13427,12 +13532,22 @@ var RecordIntegrationInstances = class {
13427
13532
  ...this.hooks.description,
13428
13533
  mintsId: true
13429
13534
  };
13430
- instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13535
+ /**
13536
+ * The record an id names. A caller holding the `integrations` registry id (the Integrations
13537
+ * page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
13538
+ * record id answers as it is. An id that names nothing comes back unchanged.
13539
+ */
13540
+ async resolveRecordId(id) {
13541
+ if (await this.handle.get(id) !== null) return id;
13542
+ const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
13543
+ return linked === void 0 ? id : linked.id;
13544
+ }
13545
+ instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
13431
13546
  listInstances = async () => {
13432
13547
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
13433
13548
  };
13434
13549
  getInstance = async ({ id }) => {
13435
- const record = await this.handle.get(id);
13550
+ const record = await this.handle.get(await this.resolveRecordId(id));
13436
13551
  return record === null ? null : this.viewOf(record);
13437
13552
  };
13438
13553
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
@@ -13449,7 +13564,8 @@ var RecordIntegrationInstances = class {
13449
13564
  await this.hooks.onChanged?.(minted, "created");
13450
13565
  return { id: minted };
13451
13566
  };
13452
- updateInstance = async ({ id, name, values }) => {
13567
+ updateInstance = async ({ id: askedId, name, values }) => {
13568
+ const id = await this.resolveRecordId(askedId);
13453
13569
  const patch = {};
13454
13570
  for (const [key, value] of Object.entries(values)) {
13455
13571
  if (value === void 0) continue;
@@ -13484,7 +13600,10 @@ var RecordIntegrationInstances = class {
13484
13600
  status: "unsupported",
13485
13601
  error: "This integration declares no connection test"
13486
13602
  };
13487
- return this.hooks.test(input);
13603
+ return this.hooks.test(input.id === null ? input : {
13604
+ ...input,
13605
+ id: await this.resolveRecordId(input.id)
13606
+ });
13488
13607
  };
13489
13608
  /** The flat provider object to hand to `registerProvider`. */
13490
13609
  toProvider() {
@@ -51995,7 +52114,14 @@ var DiscoveryCandidateSchema = object({
51995
52114
  /** `stableId` of the gateway candidate this device was found BEHIND. */
51996
52115
  parentStableId: string().optional(),
51997
52116
  /** `false` = shown for context but not pickable. Default true. */
51998
- selectable: boolean().optional()
52117
+ selectable: boolean().optional(),
52118
+ /**
52119
+ * Keys of the provider's creation form (`getChildCreationSchema`) this candidate
52120
+ * cannot be adopted without — a key for an encrypted board, a pairing code. The
52121
+ * admin asks for exactly these, on the creation form prefilled with
52122
+ * `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
52123
+ */
52124
+ needsParams: array(string()).optional()
51999
52125
  });
52000
52126
  /**
52001
52127
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -52123,7 +52249,11 @@ var deviceProviderCapability = {
52123
52249
  * pick the right type instead of assuming Camera.
52124
52250
  */
52125
52251
  getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
52126
- adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
52252
+ adoptDiscoveredDevice: method(object({
52253
+ candidate: DiscoveryCandidateSchema,
52254
+ /** The integration the operator adopts into; a provider with several instances needs it. */
52255
+ integrationId: string().optional()
52256
+ }), DeviceSummarySchema, {
52127
52257
  kind: "mutation",
52128
52258
  auth: "admin"
52129
52259
  }),
@@ -52137,7 +52267,9 @@ var deviceProviderCapability = {
52137
52267
  getChildCreationSchema: method(object({ type: _enum(DeviceType) }), CreationSchemaOutputSchema),
52138
52268
  createDevice: method(object({
52139
52269
  type: _enum(DeviceType),
52140
- config: record(string(), unknown())
52270
+ config: record(string(), unknown()),
52271
+ /** The integration the operator adds the device to; a provider with several instances needs it. */
52272
+ integrationId: string().optional()
52141
52273
  }), DeviceSummarySchema, {
52142
52274
  kind: "mutation",
52143
52275
  auth: "admin"
@@ -81826,7 +81958,7 @@ var BaseDeviceProvider = class extends BaseAddon {
81826
81958
  * `deviceClasses` instead.
81827
81959
  */
81828
81960
  async createDevice(input) {
81829
- const spec = await this.onCreateDevice(input.type, input.config);
81961
+ const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
81830
81962
  const Class = this.deviceClasses[spec.meta.type];
81831
81963
  if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
81832
81964
  const stableId = this.generateStableId(spec.meta.type, spec.config);
@@ -97436,6 +97568,15 @@ function rewriteLocation(location, currentUpstream, route) {
97436
97568
  /** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
97437
97569
  var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
97438
97570
  /**
97571
+ * Subresource Integrity pins the bytes of a script or stylesheet. The proxy rewrites vendor
97572
+ * URLs inside those bytes (and re-serves them from another origin), so the hash can no longer
97573
+ * match and the browser refuses the file: the page renders with no script at all. The hub
97574
+ * already drops the CSP that would have enforced the same pin; the attribute goes with it.
97575
+ */
97576
+ function stripIntegrity(html) {
97577
+ return html.replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
97578
+ }
97579
+ /**
97439
97580
  * Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
97440
97581
  * (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
97441
97582
  * HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
@@ -97451,6 +97592,7 @@ function rewriteBody(body, kind, currentUpstream, route) {
97451
97592
  const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
97452
97593
  out = out.replace(escaped, escapedBase);
97453
97594
  }
97595
+ if (kind === "html") out = stripIntegrity(out);
97454
97596
  const current = `${route.basePath}/${currentUpstream.host}/`;
97455
97597
  const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
97456
97598
  if (kind === "html") {
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
5981
5981
  var ZodFirstPartyTypeKind;
5982
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5983
5983
  //#endregion
5984
- //#region ../types/dist/sleep-9TIHFj1w.mjs
5984
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
5985
5985
  /**
5986
5986
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5987
5987
  * window to float samples (D455).
@@ -6790,6 +6790,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6790
6790
  }
6791
6791
  return out;
6792
6792
  }
6793
+ /** Every scope, in the order the guard and the docs list them. */
6794
+ var SCOPED_SETTINGS_SCOPES = [
6795
+ "cluster",
6796
+ "device-defaults",
6797
+ "node"
6798
+ ];
6793
6799
  /**
6794
6800
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6795
6801
  *
@@ -7681,6 +7687,13 @@ var BaseAddon = class {
7681
7687
  */
7682
7688
  secretStatePaths = [];
7683
7689
  _addonSecretTree = null;
7690
+ /**
7691
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7692
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7693
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7694
+ */
7695
+ _scopedByCap = /* @__PURE__ */ new Map();
7696
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7684
7697
  _deviceSecretTree = null;
7685
7698
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7686
7699
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7750,6 +7763,7 @@ var BaseAddon = class {
7750
7763
  this._ctx = context;
7751
7764
  this._secretSettings = null;
7752
7765
  }
7766
+ await this.loadScopedSchemas();
7753
7767
  await this.resolveConfig();
7754
7768
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7755
7769
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7759,6 +7773,7 @@ var BaseAddon = class {
7759
7773
  const normalized = normalizeAddonInitResult(result);
7760
7774
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7761
7775
  this._registeredCapNames = providers.map((p) => p.capability.name);
7776
+ await this.loadScopedSchemas();
7762
7777
  return normalized;
7763
7778
  }
7764
7779
  /**
@@ -7847,6 +7862,16 @@ var BaseAddon = class {
7847
7862
  schema(schema) {
7848
7863
  return schema;
7849
7864
  }
7865
+ /**
7866
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7867
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7868
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7869
+ * must already know which keys are per node. Names the providers register at init are added
7870
+ * once known.
7871
+ */
7872
+ scopedSettingsCapabilities() {
7873
+ return [];
7874
+ }
7850
7875
  /** Override to provide global-level settings UI schema. */
7851
7876
  globalSettingsSchema(_cap) {
7852
7877
  return null;
@@ -7890,7 +7915,8 @@ var BaseAddon = class {
7890
7915
  return [];
7891
7916
  }
7892
7917
  async getGlobalSettings(overlay, cap, nodeId) {
7893
- const schema = this.globalSettingsSchema(cap);
7918
+ await this.loadScopedSchemas(cap);
7919
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7894
7920
  if (!schema) return { sections: [] };
7895
7921
  const projected = await this.readRawProjectedStore(nodeId, cap);
7896
7922
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -8021,6 +8047,7 @@ var BaseAddon = class {
8021
8047
  * resolved blob back is a no-op for every unchanged secret.
8022
8048
  */
8023
8049
  async resolveGlobalStore(nodeId, cap) {
8050
+ await this.loadScopedSchemas(cap);
8024
8051
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8025
8052
  return this.projectForNode(raw, nodeId, cap);
8026
8053
  }
@@ -8031,6 +8058,7 @@ var BaseAddon = class {
8031
8058
  }
8032
8059
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8033
8060
  async readRawProjectedStore(nodeId, cap) {
8061
+ await this.loadScopedSchemas(cap);
8034
8062
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8035
8063
  return this.projectForNode(raw, nodeId, cap);
8036
8064
  }
@@ -8133,6 +8161,7 @@ var BaseAddon = class {
8133
8161
  await this.applyGlobalPatch(patch, nodeId);
8134
8162
  }
8135
8163
  async applyGlobalPatch(barePatch, nodeId) {
8164
+ await this.loadScopedSchemas();
8136
8165
  const keys = this.perNodeKeys();
8137
8166
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8138
8167
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8146,7 +8175,8 @@ var BaseAddon = class {
8146
8175
  await this.resolveConfig();
8147
8176
  await this.onConfigChanged();
8148
8177
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8149
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8178
+ const legacy = this.globalSettingsSchema();
8179
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8150
8180
  }
8151
8181
  /**
8152
8182
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8154,14 +8184,67 @@ var BaseAddon = class {
8154
8184
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8155
8185
  * settings API behaves exactly like the legacy node-agnostic one.
8156
8186
  */
8157
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8187
+ /**
8188
+ * Load the scoped schemas of every capability this addon declares (the override and the
8189
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8190
+ * derived from the previous set. Idempotent.
8191
+ */
8192
+ async loadScopedSchemas(extraCap) {
8193
+ const caps = new Set([
8194
+ ...this.scopedSettingsCapabilities(),
8195
+ ...this._registeredCapNames,
8196
+ ...extraCap === void 0 ? [] : [extraCap]
8197
+ ]);
8198
+ let added = false;
8199
+ for (const cap of caps) {
8200
+ if (this._scopedByCap.has(cap)) continue;
8201
+ const loaded = [];
8202
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8203
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8204
+ if (schema !== null) loaded.push({
8205
+ scope,
8206
+ schema
8207
+ });
8208
+ }
8209
+ this._scopedByCap.set(cap, loaded);
8210
+ added = true;
8211
+ }
8212
+ if (!added) return;
8213
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8214
+ this._addonSecretTree = null;
8215
+ }
8216
+ /**
8217
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8218
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8219
+ */
8220
+ scopedFormSchema(cap) {
8221
+ const byId = /* @__PURE__ */ new Map();
8222
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8223
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8224
+ }
8225
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8226
+ loadedScopedSchemas(cap) {
8227
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8228
+ return [...this._scopedByCap.values()].flat();
8229
+ }
8230
+ /**
8231
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8232
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8233
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8234
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8235
+ * Call {@link loadScopedSchemas} first (the async seams do).
8236
+ */
8158
8237
  perNodeKeys(cap) {
8159
8238
  const cacheKey = cap ?? "";
8160
- const cached = this._perNodeKeysCache.get(cacheKey);
8239
+ const cached = this._nodeKeysByCap.get(cacheKey);
8161
8240
  if (cached) return cached;
8162
- const schema = this.globalSettingsSchema(cap);
8163
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8164
- this._perNodeKeysCache.set(cacheKey, keys);
8241
+ const legacy = this.globalSettingsSchema(cap);
8242
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8243
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8244
+ if (scope !== "node") continue;
8245
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8246
+ }
8247
+ this._nodeKeysByCap.set(cacheKey, keys);
8165
8248
  return keys;
8166
8249
  }
8167
8250
  /**
@@ -8171,10 +8254,9 @@ var BaseAddon = class {
8171
8254
  * time to return its response before the addon is torn down and
8172
8255
  * re-initialised by `AddonRegistryService.restartAddon`.
8173
8256
  */
8174
- maybeAutoRestart(patchKeys, schema) {
8175
- if (!schema) return;
8257
+ maybeAutoRestart(patchKeys, schemas) {
8176
8258
  const restartKeys = /* @__PURE__ */ new Set();
8177
- for (const section of schema.sections) for (const field of section.fields) {
8259
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8178
8260
  if (field.type === "separator" || field.type === "info") continue;
8179
8261
  if (field.requiresRestart) restartKeys.add(field.key);
8180
8262
  }
@@ -8370,12 +8452,14 @@ var BaseAddon = class {
8370
8452
  * here, in the addon's own process, so the hub keeps no copy of it.
8371
8453
  */
8372
8454
  async getDeclaredSettingsKeys() {
8455
+ await this.loadScopedSchemas();
8373
8456
  return { keys: [...this.declaredKeySet()].toSorted() };
8374
8457
  }
8375
8458
  declaredKeySet() {
8376
8459
  const keys = new Set(Object.keys(this.defaults));
8377
8460
  const schema = this.globalSettingsSchema();
8378
8461
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8462
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8379
8463
  for (const key of this._declaredStateKeys) keys.add(key);
8380
8464
  for (const path of this.secretStatePaths) {
8381
8465
  const root = path.split(/[.[]/, 1)[0];
@@ -8471,7 +8555,11 @@ var BaseAddon = class {
8471
8555
  }
8472
8556
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8473
8557
  addonSecretTree() {
8474
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8558
+ if (this._addonSecretTree === null) {
8559
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8560
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8561
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8562
+ }
8475
8563
  return this._addonSecretTree;
8476
8564
  }
8477
8565
  deviceSecretTree() {
@@ -8721,6 +8809,23 @@ function dropPerNodeFields(fields) {
8721
8809
  }
8722
8810
  return kept;
8723
8811
  }
8812
+ /** The keys of the value-holding fields, recursing into layout containers. */
8813
+ function collectValueFieldKeys(fields) {
8814
+ const collected = [];
8815
+ for (const field of fields) {
8816
+ if (field.type === "group") {
8817
+ collected.push(...collectValueFieldKeys(field.fields));
8818
+ continue;
8819
+ }
8820
+ if (field.type === "sub-tabs") {
8821
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8822
+ continue;
8823
+ }
8824
+ if (field.type === "separator" || field.type === "info") continue;
8825
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8826
+ }
8827
+ return collected;
8828
+ }
8724
8829
  function collectFieldKeys(fields) {
8725
8830
  const collected = [];
8726
8831
  for (const field of fields) {
@@ -13427,12 +13532,22 @@ var RecordIntegrationInstances = class {
13427
13532
  ...this.hooks.description,
13428
13533
  mintsId: true
13429
13534
  };
13430
- instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13535
+ /**
13536
+ * The record an id names. A caller holding the `integrations` registry id (the Integrations
13537
+ * page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
13538
+ * record id answers as it is. An id that names nothing comes back unchanged.
13539
+ */
13540
+ async resolveRecordId(id) {
13541
+ if (await this.handle.get(id) !== null) return id;
13542
+ const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
13543
+ return linked === void 0 ? id : linked.id;
13544
+ }
13545
+ instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
13431
13546
  listInstances = async () => {
13432
13547
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
13433
13548
  };
13434
13549
  getInstance = async ({ id }) => {
13435
- const record = await this.handle.get(id);
13550
+ const record = await this.handle.get(await this.resolveRecordId(id));
13436
13551
  return record === null ? null : this.viewOf(record);
13437
13552
  };
13438
13553
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
@@ -13449,7 +13564,8 @@ var RecordIntegrationInstances = class {
13449
13564
  await this.hooks.onChanged?.(minted, "created");
13450
13565
  return { id: minted };
13451
13566
  };
13452
- updateInstance = async ({ id, name, values }) => {
13567
+ updateInstance = async ({ id: askedId, name, values }) => {
13568
+ const id = await this.resolveRecordId(askedId);
13453
13569
  const patch = {};
13454
13570
  for (const [key, value] of Object.entries(values)) {
13455
13571
  if (value === void 0) continue;
@@ -13484,7 +13600,10 @@ var RecordIntegrationInstances = class {
13484
13600
  status: "unsupported",
13485
13601
  error: "This integration declares no connection test"
13486
13602
  };
13487
- return this.hooks.test(input);
13603
+ return this.hooks.test(input.id === null ? input : {
13604
+ ...input,
13605
+ id: await this.resolveRecordId(input.id)
13606
+ });
13488
13607
  };
13489
13608
  /** The flat provider object to hand to `registerProvider`. */
13490
13609
  toProvider() {
@@ -51995,7 +52114,14 @@ var DiscoveryCandidateSchema = object({
51995
52114
  /** `stableId` of the gateway candidate this device was found BEHIND. */
51996
52115
  parentStableId: string().optional(),
51997
52116
  /** `false` = shown for context but not pickable. Default true. */
51998
- selectable: boolean().optional()
52117
+ selectable: boolean().optional(),
52118
+ /**
52119
+ * Keys of the provider's creation form (`getChildCreationSchema`) this candidate
52120
+ * cannot be adopted without — a key for an encrypted board, a pairing code. The
52121
+ * admin asks for exactly these, on the creation form prefilled with
52122
+ * `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
52123
+ */
52124
+ needsParams: array(string()).optional()
51999
52125
  });
52000
52126
  /**
52001
52127
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -52123,7 +52249,11 @@ var deviceProviderCapability = {
52123
52249
  * pick the right type instead of assuming Camera.
52124
52250
  */
52125
52251
  getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
52126
- adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
52252
+ adoptDiscoveredDevice: method(object({
52253
+ candidate: DiscoveryCandidateSchema,
52254
+ /** The integration the operator adopts into; a provider with several instances needs it. */
52255
+ integrationId: string().optional()
52256
+ }), DeviceSummarySchema, {
52127
52257
  kind: "mutation",
52128
52258
  auth: "admin"
52129
52259
  }),
@@ -52137,7 +52267,9 @@ var deviceProviderCapability = {
52137
52267
  getChildCreationSchema: method(object({ type: _enum(DeviceType) }), CreationSchemaOutputSchema),
52138
52268
  createDevice: method(object({
52139
52269
  type: _enum(DeviceType),
52140
- config: record(string(), unknown())
52270
+ config: record(string(), unknown()),
52271
+ /** The integration the operator adds the device to; a provider with several instances needs it. */
52272
+ integrationId: string().optional()
52141
52273
  }), DeviceSummarySchema, {
52142
52274
  kind: "mutation",
52143
52275
  auth: "admin"
@@ -81826,7 +81958,7 @@ var BaseDeviceProvider = class extends BaseAddon {
81826
81958
  * `deviceClasses` instead.
81827
81959
  */
81828
81960
  async createDevice(input) {
81829
- const spec = await this.onCreateDevice(input.type, input.config);
81961
+ const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
81830
81962
  const Class = this.deviceClasses[spec.meta.type];
81831
81963
  if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
81832
81964
  const stableId = this.generateStableId(spec.meta.type, spec.config);
@@ -97436,6 +97568,15 @@ function rewriteLocation(location, currentUpstream, route) {
97436
97568
  /** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
97437
97569
  var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
97438
97570
  /**
97571
+ * Subresource Integrity pins the bytes of a script or stylesheet. The proxy rewrites vendor
97572
+ * URLs inside those bytes (and re-serves them from another origin), so the hash can no longer
97573
+ * match and the browser refuses the file: the page renders with no script at all. The hub
97574
+ * already drops the CSP that would have enforced the same pin; the attribute goes with it.
97575
+ */
97576
+ function stripIntegrity(html) {
97577
+ return html.replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
97578
+ }
97579
+ /**
97439
97580
  * Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
97440
97581
  * (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
97441
97582
  * HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
@@ -97451,6 +97592,7 @@ function rewriteBody(body, kind, currentUpstream, route) {
97451
97592
  const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
97452
97593
  out = out.replace(escaped, escapedBase);
97453
97594
  }
97595
+ if (kind === "html") out = stripIntegrity(out);
97454
97596
  const current = `${route.basePath}/${currentUpstream.host}/`;
97455
97597
  const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
97456
97598
  if (kind === "html") {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-CO8yIFzJ.js");
3
- const require_account_integration_offline = require("../account-integration-offline-VItxeiJg.js");
2
+ const require_bosch_instances = require("../bosch-instances-Y9sMCElY.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-CeMaWElV.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");
@@ -1408,9 +1408,14 @@ function homeConnectFinishResult(account) {
1408
1408
  /**
1409
1409
  * Home Connect signs in through its own identity pages, which end on `hcauth://auth/prod?...`
1410
1410
  * — an address no browser can open. These are the hosts those pages are served from; the
1411
- * proxy reaches nothing else. (Not verified against the live pages: no vendor credentials.)
1411
+ * proxy reaches nothing else. The authorize call redirects to SingleKey ID, whose page loads its
1412
+ * scripts from `cdn.singlekey-id.com` (see `HOMECOM_LOGIN_HOSTS`).
1412
1413
  */
1413
- var HOME_CONNECT_LOGIN_HOSTS = ["api.home-connect.com", "singlekey-id.com"];
1414
+ var HOME_CONNECT_LOGIN_HOSTS = [
1415
+ "api.home-connect.com",
1416
+ "singlekey-id.com",
1417
+ "cdn.singlekey-id.com"
1418
+ ];
1414
1419
  var HOME_CONNECT_APP_SCHEME = HOME_CONNECT_REDIRECT_URI.slice(0, HOME_CONNECT_REDIRECT_URI.indexOf("://") + 3);
1415
1420
  var homeConnectSignInClaims = require_account_integration_offline.createSignInClaims();
1416
1421
  /** Start an attempt whose `authorizeUrl` is the hub-relative proxied login page. */
@@ -1,5 +1,5 @@
1
- import { $ as errMsg, B as isSecretMarkerLike, C as ingestReading, D as quantityFromUnit, E as powerMeterCapability, F as defineAddonMigrations, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, N as createEvent, O as runningFromDishwasherOperation, P as customAction, Q as DeviceType, R as entryBody, T as numericSensorCapability, U as boolean, V as _enum, W as literal, X as unknown, Y as union, Z as DeviceRole, _ as defaultIntegrationApiOver, a as BaseDevice, b as enumSensorCapability, c as RUNNING_STATE_COLD, d as VendorLoginProxy, et as EventCategory, g as consumablesCapability, h as connectionTestCapability, i as testBoschInstance, k as runningStateCapability, l as RecordIntegrationInstances, n as boschRowsPorts, o as BaseDeviceProvider, p as binaryCapability, q as record, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability, x as eventEmitterCapability, y as dishwasherCapability, z as isSecretMarker } from "../bosch-instances-BQiyzlPt.mjs";
2
- import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-nChgZUlC.mjs";
1
+ import { $ as errMsg, B as isSecretMarkerLike, C as ingestReading, D as quantityFromUnit, E as powerMeterCapability, F as defineAddonMigrations, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, N as createEvent, O as runningFromDishwasherOperation, P as customAction, Q as DeviceType, R as entryBody, T as numericSensorCapability, U as boolean, V as _enum, W as literal, X as unknown, Y as union, Z as DeviceRole, _ as defaultIntegrationApiOver, a as BaseDevice, b as enumSensorCapability, c as RUNNING_STATE_COLD, d as VendorLoginProxy, et as EventCategory, g as consumablesCapability, h as connectionTestCapability, i as testBoschInstance, k as runningStateCapability, l as RecordIntegrationInstances, n as boschRowsPorts, o as BaseDeviceProvider, p as binaryCapability, q as record, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability, x as eventEmitterCapability, y as dishwasherCapability, z as isSecretMarker } from "../bosch-instances-CIuDwXQ8.mjs";
2
+ import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-DhHNdL3k.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { connect } from "node:tls";
5
5
  import { inflateRawSync } from "node:zlib";
@@ -1407,9 +1407,14 @@ function homeConnectFinishResult(account) {
1407
1407
  /**
1408
1408
  * Home Connect signs in through its own identity pages, which end on `hcauth://auth/prod?...`
1409
1409
  * — an address no browser can open. These are the hosts those pages are served from; the
1410
- * proxy reaches nothing else. (Not verified against the live pages: no vendor credentials.)
1410
+ * proxy reaches nothing else. The authorize call redirects to SingleKey ID, whose page loads its
1411
+ * scripts from `cdn.singlekey-id.com` (see `HOMECOM_LOGIN_HOSTS`).
1411
1412
  */
1412
- var HOME_CONNECT_LOGIN_HOSTS = ["api.home-connect.com", "singlekey-id.com"];
1413
+ var HOME_CONNECT_LOGIN_HOSTS = [
1414
+ "api.home-connect.com",
1415
+ "singlekey-id.com",
1416
+ "cdn.singlekey-id.com"
1417
+ ];
1413
1418
  var HOME_CONNECT_APP_SCHEME = HOME_CONNECT_REDIRECT_URI.slice(0, HOME_CONNECT_REDIRECT_URI.indexOf("://") + 3);
1414
1419
  var homeConnectSignInClaims = createSignInClaims();
1415
1420
  /** Start an attempt whose `authorizeUrl` is the hub-relative proxied login page. */
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-CO8yIFzJ.js");
3
- const require_account_integration_offline = require("../account-integration-offline-VItxeiJg.js");
2
+ const require_bosch_instances = require("../bosch-instances-Y9sMCElY.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-CeMaWElV.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  //#region src/homecom/config.ts
6
6
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -1227,8 +1227,13 @@ function finishedFromGateways(tokens, resolved, listFailure) {
1227
1227
  var homeComSignInAttempts = require_account_integration_offline.createSignInAttemptStore();
1228
1228
  //#endregion
1229
1229
  //#region src/homecom/homecom-sign-in-proxy.ts
1230
- /** SingleKey ID serves the whole HomeCom login. (Not verified against the live pages: no vendor credentials.) */
1231
- var HOMECOM_LOGIN_HOSTS = ["singlekey-id.com"];
1230
+ /**
1231
+ * SingleKey ID serves the HomeCom login page; its scripts and styles come from `cdn.singlekey-id.com`
1232
+ * as `type=module` + `crossorigin` files, which the CDN does not CORS-enable for another origin —
1233
+ * served from the hub origin through the proxy they load, loaded directly they are refused and the
1234
+ * page has no script (only the email field, no way to continue).
1235
+ */
1236
+ var HOMECOM_LOGIN_HOSTS = ["singlekey-id.com", "cdn.singlekey-id.com"];
1232
1237
  var HOMECOM_APP_SCHEME = HOMECOM_REDIRECT_URI.slice(0, HOMECOM_REDIRECT_URI.indexOf("://") + 3);
1233
1238
  var homeComSignInClaims = require_account_integration_offline.createSignInClaims();
1234
1239
  /** Start an attempt whose `authorizeUrl` is the hub-relative proxied login page. */
@@ -1,5 +1,5 @@
1
- import { $ as errMsg, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, M as waterHeaterCapability, P as customAction, Q as DeviceType, R as entryBody, U as boolean, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, d as VendorLoginProxy, et as EventCategory, h as connectionTestCapability, i as testBoschInstance, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-BQiyzlPt.mjs";
2
- import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-nChgZUlC.mjs";
1
+ import { $ as errMsg, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, M as waterHeaterCapability, P as customAction, Q as DeviceType, R as entryBody, U as boolean, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, d as VendorLoginProxy, et as EventCategory, h as connectionTestCapability, i as testBoschInstance, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-CIuDwXQ8.mjs";
2
+ import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-DhHNdL3k.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  //#region src/homecom/config.ts
5
5
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -1226,8 +1226,13 @@ function finishedFromGateways(tokens, resolved, listFailure) {
1226
1226
  var homeComSignInAttempts = createSignInAttemptStore();
1227
1227
  //#endregion
1228
1228
  //#region src/homecom/homecom-sign-in-proxy.ts
1229
- /** SingleKey ID serves the whole HomeCom login. (Not verified against the live pages: no vendor credentials.) */
1230
- var HOMECOM_LOGIN_HOSTS = ["singlekey-id.com"];
1229
+ /**
1230
+ * SingleKey ID serves the HomeCom login page; its scripts and styles come from `cdn.singlekey-id.com`
1231
+ * as `type=module` + `crossorigin` files, which the CDN does not CORS-enable for another origin —
1232
+ * served from the hub origin through the proxy they load, loaded directly they are refused and the
1233
+ * page has no script (only the email field, no way to continue).
1234
+ */
1235
+ var HOMECOM_LOGIN_HOSTS = ["singlekey-id.com", "cdn.singlekey-id.com"];
1231
1236
  var HOMECOM_APP_SCHEME = HOMECOM_REDIRECT_URI.slice(0, HOMECOM_REDIRECT_URI.indexOf("://") + 3);
1232
1237
  var homeComSignInClaims = createSignInClaims();
1233
1238
  /** Start an attempt whose `authorizeUrl` is the hub-relative proxied login page. */
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-CO8yIFzJ.js");
5
+ const require_bosch_instances = require("../bosch-instances-Y9sMCElY.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");
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, A as switchCapability, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, P as customAction, Q as DeviceType, R as entryBody, S as heatingValveCapability, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, et as EventCategory, f as batteryCapability, h as connectionTestCapability, i as testBoschInstance, j as temperatureSensorCapability, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-BQiyzlPt.mjs";
1
+ import { $ as errMsg, A as switchCapability, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, P as customAction, Q as DeviceType, R as entryBody, S as heatingValveCapability, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, et as EventCategory, f as batteryCapability, h as connectionTestCapability, i as testBoschInstance, j as temperatureSensorCapability, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-CIuDwXQ8.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
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",
@@ -141,7 +141,7 @@
141
141
  }
142
142
  ],
143
143
  "requires": {
144
- "server": ">=1.2.770"
144
+ "server": ">=1.2.778"
145
145
  }
146
146
  },
147
147
  "files": [