@camstack/addon-provider-bosch 0.1.34 → 0.1.36

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,4 +1,4 @@
1
- import { A as customAction, C as switchCapability, D as DeviceType, F as array, L as literal, O as errMsg, P as _enum, R as number, V as string, W as EventCategory, f as deviceAdoptionCapability, g as heatingValveCapability, j as defineCustomActions, l as climateControlCapability, n as BaseDeviceProvider, s as batteryCapability, t as BaseDevice, u as connectionTestCapability, w as temperatureSensorCapability, z as object } from "../dist-BV6iLwfA.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-BIvOtuzj.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
@@ -198,6 +198,109 @@ function buildConnectionFormSchema() {
198
198
  }] };
199
199
  }
200
200
  //#endregion
201
+ //#region src/shc/shc-instances.ts
202
+ /**
203
+ * Bosch Smart Home Controller integrations as `integration-instances` (planning#416, D985,
204
+ * D1007): one `connections` record per controller under the `integrations` registry id. The body
205
+ * IS the connection (host, the client certificate and key and the optional token, all three in
206
+ * the keychain), so the form the instances page edits is the stored shape.
207
+ */
208
+ var shcConnectionsEntryKind = defineEntryKind("connections", {
209
+ title: "Smart Home Controllers",
210
+ schema: entryBody({
211
+ host: string().optional(),
212
+ sslCertificate: string().optional().meta({ secret: true }),
213
+ sslKey: string().optional().meta({ secret: true }),
214
+ token: string().optional().meta({ secret: true })
215
+ }),
216
+ writeTrigger: "an operator pairs, edits or removes a Smart Home Controller"
217
+ });
218
+ /** Every kind this addon declares, keyed by its name. */
219
+ var shcRecordKinds = { connections: shcConnectionsEntryKind };
220
+ var LEGACY_KEYS = [
221
+ "host",
222
+ "sslCertificate",
223
+ "sslKey",
224
+ "token"
225
+ ];
226
+ var SECRET_KEYS = [
227
+ "sslCertificate",
228
+ "sslKey",
229
+ "token"
230
+ ];
231
+ function text(value) {
232
+ return typeof value === "string" && value.length > 0 ? value : void 0;
233
+ }
234
+ /** The record body of one controller, from its legacy rows (secrets resolved). */
235
+ function shcBodyFromRows(values, log, integration) {
236
+ const dropped = Object.keys(values).filter((key) => !LEGACY_KEYS.includes(key));
237
+ if (dropped.length > 0) log.warn(`integration '${integration.id}': legacy settings nothing reads any more are dropped: ${dropped.join(", ")}`);
238
+ const host = text(values["host"]);
239
+ const sslCertificate = text(values["sslCertificate"]);
240
+ const sslKey = text(values["sslKey"]);
241
+ const token = text(values["token"]);
242
+ return {
243
+ name: integration.name,
244
+ ...host !== void 0 ? { host } : {},
245
+ ...sslCertificate !== void 0 ? { sslCertificate } : {},
246
+ ...sslKey !== void 0 ? { sslKey } : {},
247
+ ...token !== void 0 ? { token } : {}
248
+ };
249
+ }
250
+ /** The whole `bosch-shc` catalogue (D987): offered to the engine at `onInitialize`. */
251
+ var SHC_MIGRATIONS = [boschRowsMigration({
252
+ addonId: "bosch-shc",
253
+ id: "bosch-shc/2026-11-05-integration-rows-to-record",
254
+ description: "Move every Smart Home Controller connection into a typed instance record keyed by its registry id, then clear the legacy integration settings rows and their keychain values",
255
+ fixtureMark: "a legacy setting key the migration does not know",
256
+ kind: shcConnectionsEntryKind,
257
+ bodyFrom: shcBodyFromRows
258
+ })];
259
+ function shcRowsPorts(deps) {
260
+ return boschRowsPorts({
261
+ ...deps,
262
+ kind: shcConnectionsEntryKind
263
+ });
264
+ }
265
+ /**
266
+ * The live connection of one controller, read from its record. null while the connection is
267
+ * incomplete; a locked keychain throws so the caller keeps the client it has instead of
268
+ * reconfiguring it without its certificate (D49).
269
+ */
270
+ async function shcConnectionOf(instances, id) {
271
+ const read = await instances.readResolved(id);
272
+ if (read === null) return null;
273
+ if (read.secrets === "unavailable") throw new Error("the keychain is locked, the saved controller credentials cannot be read");
274
+ return connectionFromSettings(read.body);
275
+ }
276
+ function buildShcInstances(records, deps) {
277
+ const handle = records.kind(shcConnectionsEntryKind);
278
+ const instances = new RecordIntegrationInstances(records, shcConnectionsEntryKind, {
279
+ description: {
280
+ role: "devices",
281
+ instanceMode: "multiple",
282
+ kind: "controller",
283
+ canTest: true
284
+ },
285
+ formSchema: () => buildConnectionFormSchema(),
286
+ initialBody: (name) => ({ name }),
287
+ configured: (body) => connectionFromSettings(body) !== null,
288
+ onChanged: async (_id, change) => {
289
+ if (change !== "deleted") await deps.onChanged();
290
+ },
291
+ test: (input) => testBoschInstance({
292
+ ...input,
293
+ tester: deps.tester,
294
+ handle,
295
+ secretKeys: SECRET_KEYS
296
+ })
297
+ });
298
+ return {
299
+ instances,
300
+ provider: keepBodyKeys(instances.toProvider(), new Set(Object.keys(shcConnectionsEntryKind.schema.shape)))
301
+ };
302
+ }
303
+ //#endregion
201
304
  //#region src/shc/shc-self-signed.ts
202
305
  /**
203
306
  * RSA-2048 self-signed certificate. The controller stores this certificate;
@@ -23732,11 +23835,25 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23732
23835
  [DeviceType.Switch]: ShcSwitchDevice
23733
23836
  };
23734
23837
  clients = null;
23838
+ instances = null;
23735
23839
  constructor() {
23736
23840
  super({});
23737
23841
  }
23738
23842
  async onInitialize() {
23739
23843
  const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
23844
+ const records = this.ctx.records;
23845
+ if (records === void 0) throw new Error("bosch-shc: ctx.records is required (hub-only addon)");
23846
+ await records.declare(shcRecordKinds);
23847
+ await this.runMigrations(defineAddonMigrations(SHC_MIGRATIONS, shcRowsPorts({
23848
+ api: defaultIntegrationApiOver(this.ctx.api),
23849
+ records,
23850
+ warn: (message) => this.ctx.logger.warn(message)
23851
+ })));
23852
+ const built = buildShcInstances(records, {
23853
+ onChanged: () => this.reconcileIntegrations(),
23854
+ tester: this.buildConnectionTestProvider()
23855
+ });
23856
+ this.instances = built.instances;
23740
23857
  this.clients = new ShcClientRegistry(this.ctx.logger);
23741
23858
  this.clients.setOnConnected((integrationId) => {
23742
23859
  this.setIntegrationDevicesOnline(integrationId, true);
@@ -23756,6 +23873,10 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23756
23873
  {
23757
23874
  capability: connectionTestCapability,
23758
23875
  provider: this.buildConnectionTestProvider()
23876
+ },
23877
+ {
23878
+ capability: integrationInstancesCapability,
23879
+ provider: built.provider
23759
23880
  }
23760
23881
  ],
23761
23882
  customActions: boschShcActions,
@@ -23797,11 +23918,9 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23797
23918
  this.ctx.logger.warn("Bosch SHC: shutdown error", { meta: { error: errMsg(err) } });
23798
23919
  }
23799
23920
  this.clients = null;
23921
+ this.instances = null;
23800
23922
  await super.onShutdown();
23801
23923
  }
23802
- globalSettingsSchema() {
23803
- return buildConnectionFormSchema();
23804
- }
23805
23924
  async supportsManualCreation() {
23806
23925
  return false;
23807
23926
  }
@@ -23844,7 +23963,7 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23844
23963
  for (const integration of all) {
23845
23964
  if (integration.addonId !== this.ctx.id) continue;
23846
23965
  try {
23847
- const connection = connectionFromSettings(await this.ctx.api.integrations.getSettings.query({ id: integration.id }));
23966
+ const connection = await shcConnectionOf(this.requireInstances(), integration.id);
23848
23967
  if (connection) hosts.push(connection.host);
23849
23968
  } catch (err) {
23850
23969
  this.ctx.logger.warn("SHC discovery: settings read failed", { meta: {
@@ -23859,6 +23978,10 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23859
23978
  return [];
23860
23979
  }
23861
23980
  }
23981
+ requireInstances() {
23982
+ if (!this.instances) throw new Error("Bosch SHC provider not initialised");
23983
+ return this.instances;
23984
+ }
23862
23985
  requireClients() {
23863
23986
  if (!this.clients) throw new Error("Bosch SHC provider not initialised");
23864
23987
  return this.clients;
@@ -23869,7 +23992,7 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23869
23992
  const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id && i.enabled);
23870
23993
  const surviving = /* @__PURE__ */ new Set();
23871
23994
  for (const integration of mine) try {
23872
- const connection = connectionFromSettings(await this.ctx.api.integrations.getSettings.query({ id: integration.id }));
23995
+ const connection = await shcConnectionOf(this.requireInstances(), integration.id);
23873
23996
  if (!connection) continue;
23874
23997
  await reg.upsert(integration.id, integration.name, connection);
23875
23998
  surviving.add(integration.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
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",
@@ -36,6 +36,7 @@
36
36
  "addons": [
37
37
  {
38
38
  "id": "bosch-shc",
39
+ "integrationRole": "devices",
39
40
  "category": "providers",
40
41
  "name": "Bosch Smart Home",
41
42
  "version": "0.1.0",
@@ -57,11 +58,15 @@
57
58
  },
58
59
  {
59
60
  "name": "connection-test"
61
+ },
62
+ {
63
+ "name": "integration-instances"
60
64
  }
61
65
  ]
62
66
  },
63
67
  {
64
68
  "id": "bosch-home-connect",
69
+ "integrationRole": "devices",
65
70
  "category": "providers",
66
71
  "name": "Bosch Home Connect",
67
72
  "version": "0.1.0",
@@ -84,6 +89,9 @@
84
89
  {
85
90
  "name": "connection-test"
86
91
  },
92
+ {
93
+ "name": "integration-instances"
94
+ },
87
95
  {
88
96
  "name": "dishwasher"
89
97
  },
@@ -97,6 +105,7 @@
97
105
  },
98
106
  {
99
107
  "id": "bosch-homecom",
108
+ "integrationRole": "devices",
100
109
  "category": "providers",
101
110
  "name": "Bosch HomeCom",
102
111
  "version": "0.1.0",
@@ -119,6 +128,9 @@
119
128
  {
120
129
  "name": "connection-test"
121
130
  },
131
+ {
132
+ "name": "integration-instances"
133
+ },
122
134
  {
123
135
  "name": "climate-control"
124
136
  },
@@ -127,7 +139,10 @@
127
139
  }
128
140
  ]
129
141
  }
130
- ]
142
+ ],
143
+ "requires": {
144
+ "server": ">=1.2.765"
145
+ }
131
146
  },
132
147
  "files": [
133
148
  "dist",