@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.
- package/CHANGELOG.md +12 -0
- package/dist/{account-integration-offline-R-1-uYH5.mjs → account-integration-offline-9ooJuNrN.mjs} +1 -1
- package/dist/{account-integration-offline-B7f80oHo.js → account-integration-offline-Bnm17sAE.js} +2 -2
- package/dist/{dist-DOr0p9V1.js → bosch-instances-BIvOtuzj.mjs} +34161 -30835
- package/dist/{dist-BV6iLwfA.mjs → bosch-instances-CXmuRkef.js} +36010 -32098
- package/dist/home-connect/addon.js +348 -278
- package/dist/home-connect/addon.mjs +174 -104
- package/dist/homecom/addon.js +246 -117
- package/dist/homecom/addon.mjs +164 -35
- package/dist/shc/addon.js +216 -93
- package/dist/shc/addon.mjs +129 -6
- package/package.json +17 -2
package/dist/homecom/addon.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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-BIvOtuzj.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-9ooJuNrN.mjs";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
//#region src/homecom/config.ts
|
|
5
5
|
var HOMECOM_BRANDS = ["bosch", "buderus"];
|
|
@@ -41,11 +41,6 @@ var HOMECOM_GATEWAYS_PATH = "/pointt-api/api/v1/gateways/";
|
|
|
41
41
|
function homeComApiBase(_region) {
|
|
42
42
|
return HOMECOM_POINTT_API_ORIGIN;
|
|
43
43
|
}
|
|
44
|
-
var HOMECOM_SECRET_KEYS = [
|
|
45
|
-
"username",
|
|
46
|
-
"token",
|
|
47
|
-
"refresh"
|
|
48
|
-
];
|
|
49
44
|
var HIDDEN_UNTIL_ADVANCED = {
|
|
50
45
|
field: "__hidden__",
|
|
51
46
|
equals: "yes"
|
|
@@ -1262,37 +1257,23 @@ function beginHomeComProxiedSignIn(deps) {
|
|
|
1262
1257
|
}
|
|
1263
1258
|
//#endregion
|
|
1264
1259
|
//#region src/homecom/homecom-token-persist.ts
|
|
1265
|
-
function unreadable(value) {
|
|
1266
|
-
return isSecretMarker(value) || isSecretMarkerLike(value);
|
|
1267
|
-
}
|
|
1268
1260
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
* addon could not read, so the write would only fail.
|
|
1261
|
+
* Patch the integration's record, one write at a time per integration. Refuses (and says so,
|
|
1262
|
+
* without values) when the record's secrets cannot be read right now: the write would replace a
|
|
1263
|
+
* secret the addon cannot see.
|
|
1273
1264
|
*/
|
|
1274
1265
|
function buildSerialPatcher(deps) {
|
|
1275
1266
|
const tails = /* @__PURE__ */ new Map();
|
|
1276
1267
|
return (integrationId, patch) => {
|
|
1277
1268
|
const run = async () => {
|
|
1278
1269
|
try {
|
|
1279
|
-
const stored = await deps.
|
|
1280
|
-
|
|
1281
|
-
if (locked
|
|
1282
|
-
deps.logger.warn(`HomeCom not persisting ${deps.what} — stored secrets are locked`, {
|
|
1283
|
-
tags: { integrationId },
|
|
1284
|
-
meta: { locked }
|
|
1285
|
-
});
|
|
1270
|
+
const stored = await deps.read(integrationId);
|
|
1271
|
+
if (stored === null) return;
|
|
1272
|
+
if (stored.locked) {
|
|
1273
|
+
deps.logger.warn(`HomeCom not persisting ${deps.what} — stored secrets are locked`, { tags: { integrationId } });
|
|
1286
1274
|
return;
|
|
1287
1275
|
}
|
|
1288
|
-
await deps.
|
|
1289
|
-
id: integrationId,
|
|
1290
|
-
settings: {
|
|
1291
|
-
...stored,
|
|
1292
|
-
...patch
|
|
1293
|
-
},
|
|
1294
|
-
secretKeys: [...HOMECOM_SECRET_KEYS]
|
|
1295
|
-
});
|
|
1276
|
+
await deps.patch(integrationId, patch);
|
|
1296
1277
|
} catch (err) {
|
|
1297
1278
|
deps.logger.warn(`HomeCom failed to persist ${deps.what}`, {
|
|
1298
1279
|
tags: { integrationId },
|
|
@@ -1876,6 +1857,134 @@ function buildHomeComConnectionTestProvider(deps) {
|
|
|
1876
1857
|
};
|
|
1877
1858
|
}
|
|
1878
1859
|
//#endregion
|
|
1860
|
+
//#region src/homecom/homecom-instances.ts
|
|
1861
|
+
/**
|
|
1862
|
+
* HomeCom integrations as `integration-instances` (planning#416, D985, D1007): one `connections`
|
|
1863
|
+
* record per account under the `integrations` registry id. The body IS the account (the account
|
|
1864
|
+
* email and the OAuth tokens in the keychain; brand, region, the chosen gateway and the
|
|
1865
|
+
* signed-in label in the open), so the form the instances page edits is the stored shape.
|
|
1866
|
+
*/
|
|
1867
|
+
var homeComConnectionsEntryKind = defineEntryKind("connections", {
|
|
1868
|
+
title: "HomeCom accounts",
|
|
1869
|
+
schema: entryBody({
|
|
1870
|
+
username: string().optional().meta({ secret: true }),
|
|
1871
|
+
token: string().optional().meta({ secret: true }),
|
|
1872
|
+
refresh: string().optional().meta({ secret: true }),
|
|
1873
|
+
brand: HomeComBrandSchema.optional(),
|
|
1874
|
+
region: HomeComRegionSchema.optional(),
|
|
1875
|
+
gatewayId: string().optional(),
|
|
1876
|
+
accountLabel: string().optional(),
|
|
1877
|
+
gatewayCount: number().optional()
|
|
1878
|
+
}),
|
|
1879
|
+
writeTrigger: "an operator signs in, edits or removes a HomeCom account, or a connection stores a rotated token or its gateway"
|
|
1880
|
+
});
|
|
1881
|
+
/** Every kind this addon declares, keyed by its name. */
|
|
1882
|
+
var homeComRecordKinds = { connections: homeComConnectionsEntryKind };
|
|
1883
|
+
var LEGACY_KEYS = [
|
|
1884
|
+
"username",
|
|
1885
|
+
"token",
|
|
1886
|
+
"refresh",
|
|
1887
|
+
"brand",
|
|
1888
|
+
"region",
|
|
1889
|
+
"gatewayId",
|
|
1890
|
+
"accountLabel",
|
|
1891
|
+
"gatewayCount"
|
|
1892
|
+
];
|
|
1893
|
+
var SECRET_KEYS = [
|
|
1894
|
+
"username",
|
|
1895
|
+
"token",
|
|
1896
|
+
"refresh"
|
|
1897
|
+
];
|
|
1898
|
+
function text(value) {
|
|
1899
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1900
|
+
}
|
|
1901
|
+
/** The record body of one account, from its legacy rows (secrets resolved). */
|
|
1902
|
+
function homeComBodyFromRows(values, log, integration) {
|
|
1903
|
+
const dropped = Object.keys(values).filter((key) => !LEGACY_KEYS.includes(key));
|
|
1904
|
+
if (dropped.length > 0) log.warn(`integration '${integration.id}': legacy settings nothing reads any more are dropped: ${dropped.join(", ")}`);
|
|
1905
|
+
const username = text(values["username"]);
|
|
1906
|
+
const token = text(values["token"]);
|
|
1907
|
+
const refresh = text(values["refresh"]);
|
|
1908
|
+
const brand = HomeComBrandSchema.safeParse(values["brand"]);
|
|
1909
|
+
const region = HomeComRegionSchema.safeParse(values["region"]);
|
|
1910
|
+
const gatewayId = text(values["gatewayId"])?.trim();
|
|
1911
|
+
const accountLabel = text(values["accountLabel"]);
|
|
1912
|
+
const count = values["gatewayCount"];
|
|
1913
|
+
return {
|
|
1914
|
+
name: integration.name,
|
|
1915
|
+
...username !== void 0 ? { username } : {},
|
|
1916
|
+
...token !== void 0 ? { token } : {},
|
|
1917
|
+
...refresh !== void 0 ? { refresh } : {},
|
|
1918
|
+
...brand.success ? { brand: brand.data } : {},
|
|
1919
|
+
...region.success ? { region: region.data } : {},
|
|
1920
|
+
...gatewayId !== void 0 && gatewayId.length > 0 ? { gatewayId } : {},
|
|
1921
|
+
...accountLabel !== void 0 ? { accountLabel } : {},
|
|
1922
|
+
...typeof count === "number" && Number.isFinite(count) ? { gatewayCount: count } : {}
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
/** The whole `bosch-homecom` catalogue (D987): offered to the engine at `onInitialize`. */
|
|
1926
|
+
var HOMECOM_MIGRATIONS = [boschRowsMigration({
|
|
1927
|
+
addonId: "bosch-homecom",
|
|
1928
|
+
id: "bosch-homecom/2026-11-05-integration-rows-to-record",
|
|
1929
|
+
description: "Move every HomeCom account into a typed instance record keyed by its registry id, then clear the legacy integration settings rows and their keychain values",
|
|
1930
|
+
fixtureMark: "a legacy setting key the migration does not know",
|
|
1931
|
+
kind: homeComConnectionsEntryKind,
|
|
1932
|
+
bodyFrom: homeComBodyFromRows
|
|
1933
|
+
})];
|
|
1934
|
+
function homeComRowsPorts(deps) {
|
|
1935
|
+
return boschRowsPorts({
|
|
1936
|
+
...deps,
|
|
1937
|
+
kind: homeComConnectionsEntryKind
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
async function homeComStoredOf(instances, id) {
|
|
1941
|
+
const read = await instances.readResolved(id);
|
|
1942
|
+
if (read === null) return null;
|
|
1943
|
+
return {
|
|
1944
|
+
settings: { ...read.body },
|
|
1945
|
+
locked: read.secrets === "unavailable"
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* The live account of one integration, read from its record. null while it is not signed in; a
|
|
1950
|
+
* locked keychain throws so the caller keeps the client it has instead of reconfiguring it
|
|
1951
|
+
* without its tokens (D49).
|
|
1952
|
+
*/
|
|
1953
|
+
async function homeComConnectionOf(instances, id) {
|
|
1954
|
+
const stored = await homeComStoredOf(instances, id);
|
|
1955
|
+
if (stored === null) return null;
|
|
1956
|
+
if (stored.locked) throw new Error("the keychain is locked, the saved HomeCom tokens cannot be read");
|
|
1957
|
+
return connectionFromSettings(stored.settings);
|
|
1958
|
+
}
|
|
1959
|
+
function buildHomeComInstances(records, deps) {
|
|
1960
|
+
const handle = records.kind(homeComConnectionsEntryKind);
|
|
1961
|
+
const instances = new RecordIntegrationInstances(records, homeComConnectionsEntryKind, {
|
|
1962
|
+
description: {
|
|
1963
|
+
role: "devices",
|
|
1964
|
+
instanceMode: "multiple",
|
|
1965
|
+
kind: "account",
|
|
1966
|
+
canTest: true
|
|
1967
|
+
},
|
|
1968
|
+
formSchema: () => buildHomeComFormSchema(),
|
|
1969
|
+
initialBody: (name) => ({ name }),
|
|
1970
|
+
configured: (body) => connectionFromSettings(body) !== null,
|
|
1971
|
+
onChanged: async (_id, change) => {
|
|
1972
|
+
if (change !== "deleted") await deps.onChanged();
|
|
1973
|
+
},
|
|
1974
|
+
test: (input) => testBoschInstance({
|
|
1975
|
+
...input,
|
|
1976
|
+
tester: deps.tester,
|
|
1977
|
+
handle,
|
|
1978
|
+
secretKeys: SECRET_KEYS
|
|
1979
|
+
})
|
|
1980
|
+
});
|
|
1981
|
+
return {
|
|
1982
|
+
instances,
|
|
1983
|
+
handle,
|
|
1984
|
+
provider: keepBodyKeys(instances.toProvider(), new Set(Object.keys(homeComConnectionsEntryKind.schema.shape)))
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
//#endregion
|
|
1879
1988
|
//#region src/homecom/devices/homecom-climate-device.ts
|
|
1880
1989
|
var homeComClimateSchema = object({
|
|
1881
1990
|
nativeId: string().min(1),
|
|
@@ -2070,6 +2179,7 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2070
2179
|
[DeviceType.WaterHeater]: HomeComWaterHeaterDevice
|
|
2071
2180
|
};
|
|
2072
2181
|
clients = null;
|
|
2182
|
+
instances = null;
|
|
2073
2183
|
loginProxy = null;
|
|
2074
2184
|
loginPlane = null;
|
|
2075
2185
|
constructor() {
|
|
@@ -2077,10 +2187,23 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2077
2187
|
}
|
|
2078
2188
|
async onInitialize() {
|
|
2079
2189
|
const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
|
|
2190
|
+
const records = this.ctx.records;
|
|
2191
|
+
if (records === void 0) throw new Error("bosch-homecom: ctx.records is required (hub-only addon)");
|
|
2192
|
+
await records.declare(homeComRecordKinds);
|
|
2193
|
+
await this.runMigrations(defineAddonMigrations(HOMECOM_MIGRATIONS, homeComRowsPorts({
|
|
2194
|
+
api: defaultIntegrationApiOver(this.ctx.api),
|
|
2195
|
+
records,
|
|
2196
|
+
warn: (message) => this.ctx.logger.warn(message)
|
|
2197
|
+
})));
|
|
2198
|
+
const built = buildHomeComInstances(records, {
|
|
2199
|
+
onChanged: () => this.reconcileIntegrations(),
|
|
2200
|
+
tester: this.buildConnectionTestProvider()
|
|
2201
|
+
});
|
|
2202
|
+
this.instances = built;
|
|
2080
2203
|
await this.serveLoginProxy();
|
|
2081
2204
|
const persistDeps = {
|
|
2082
|
-
|
|
2083
|
-
|
|
2205
|
+
read: (id) => homeComStoredOf(built.instances, id),
|
|
2206
|
+
patch: (id, patch) => built.handle.patch(id, patch),
|
|
2084
2207
|
logger: this.ctx.logger
|
|
2085
2208
|
};
|
|
2086
2209
|
const persistTokens = buildHomeComTokenPersister(persistDeps);
|
|
@@ -2107,6 +2230,10 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2107
2230
|
{
|
|
2108
2231
|
capability: connectionTestCapability,
|
|
2109
2232
|
provider: this.buildConnectionTestProvider()
|
|
2233
|
+
},
|
|
2234
|
+
{
|
|
2235
|
+
capability: integrationInstancesCapability,
|
|
2236
|
+
provider: built.provider
|
|
2110
2237
|
}
|
|
2111
2238
|
],
|
|
2112
2239
|
customActions: homeComActions,
|
|
@@ -2164,6 +2291,7 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2164
2291
|
this.ctx.logger.warn("HomeCom shutdown error", { meta: { error: errMsg(err) } });
|
|
2165
2292
|
}
|
|
2166
2293
|
this.clients = null;
|
|
2294
|
+
this.instances = null;
|
|
2167
2295
|
await super.onShutdown();
|
|
2168
2296
|
}
|
|
2169
2297
|
requireClients() {
|
|
@@ -2183,7 +2311,7 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2183
2311
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id && i.enabled);
|
|
2184
2312
|
const surviving = /* @__PURE__ */ new Set();
|
|
2185
2313
|
for (const integration of mine) try {
|
|
2186
|
-
const connection =
|
|
2314
|
+
const connection = await homeComConnectionOf(this.requireInstances().instances, integration.id);
|
|
2187
2315
|
if (!connection) continue;
|
|
2188
2316
|
await reg.upsert(integration.id, integration.name, connection);
|
|
2189
2317
|
surviving.add(integration.id);
|
|
@@ -2272,9 +2400,6 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2272
2400
|
logger: this.ctx.logger
|
|
2273
2401
|
});
|
|
2274
2402
|
}
|
|
2275
|
-
globalSettingsSchema() {
|
|
2276
|
-
return buildHomeComFormSchema();
|
|
2277
|
-
}
|
|
2278
2403
|
async supportsManualCreation() {
|
|
2279
2404
|
return false;
|
|
2280
2405
|
}
|
|
@@ -2284,6 +2409,10 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
2284
2409
|
async onCreateDevice(_type, _config) {
|
|
2285
2410
|
throw new Error("HomeCom devices are adopted from the cloud");
|
|
2286
2411
|
}
|
|
2412
|
+
requireInstances() {
|
|
2413
|
+
if (!this.instances) throw new Error("HomeCom provider not initialised");
|
|
2414
|
+
return this.instances;
|
|
2415
|
+
}
|
|
2287
2416
|
setIntegrationDevicesOnline(integrationId, online) {
|
|
2288
2417
|
const reg = this.ctx.kernel.deviceRegistry;
|
|
2289
2418
|
if (!reg) return;
|