@camstack/addon-provider-bosch 0.1.54 → 0.1.56
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-DK6CYsvB.js → account-integration-offline-DOH2c7x2.js} +1 -1
- package/dist/{account-integration-offline-C9glrBvy.mjs → account-integration-offline-fB8V7XNu.mjs} +1 -1
- package/dist/{bosch-instances-DkTzZrlR.js → bosch-instances-BnuV5On6.js} +15744 -3747
- package/dist/{bosch-instances-DMzZSMxW.mjs → bosch-instances-REcWLM14.mjs} +15709 -3748
- package/dist/home-connect/addon.js +2 -2
- package/dist/home-connect/addon.mjs +2 -2
- package/dist/homecom/addon.js +267 -48
- package/dist/homecom/addon.mjs +267 -48
- package/dist/shc/addon.js +1 -1
- package/dist/shc/addon.mjs +1 -1
- package/package.json +2 -2
package/dist/homecom/addon.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
1
|
+
import { $ as literal, A as registerPowerSwitch, B as defineCustomActions, F as waterHeaterCapability, H as entryBody, I as RecordIntegrationInstances, J as _enum, K as rowsDepsOver, L as SECRET_CLEAR, Q as custom, T as integrationPageHref, U as isSecretFieldView, V as defineEntryKind, X as array, Y as _null, Z as boolean, a as BaseDevice, b as heatPumpControlCapability, ct as errMsg, et as number, f as climateControlCapability, h as deviceAdoptionCapability, i as testBoschInstance, it as union, l as SessionHealthTracker, lt as EventCategory, n as boschRowsPorts, nt as record, o as BaseDeviceProvider, ot as DeviceRole, q as toSecretFieldView, r as keepBodyKeys, rt as string, st as DeviceType, t as boschRowsMigration, tt as object, w as integrationInstancesCapability, x as heatPumpStatusCapability, y as gatewayDeviceName, z as customAction } from "../bosch-instances-REcWLM14.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-fB8V7XNu.mjs";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
@@ -457,6 +457,17 @@ async function exploreHomeComResources(input) {
|
|
|
457
457
|
}
|
|
458
458
|
//#endregion
|
|
459
459
|
//#region src/homecom/custom-actions.ts
|
|
460
|
+
/** A stored secret as the form sees it (`SecretFieldView`): set or not, never the value. */
|
|
461
|
+
var secretView = custom(isSecretFieldView, "a stored secret view");
|
|
462
|
+
/**
|
|
463
|
+
* A sign-in secret in the result: in clear only for a form that has no record yet (the wizard
|
|
464
|
+
* stores it on create); for an existing instance it is already stored and comes back as a view.
|
|
465
|
+
*/
|
|
466
|
+
var signInSecret = union([
|
|
467
|
+
string().min(1),
|
|
468
|
+
secretView,
|
|
469
|
+
_null()
|
|
470
|
+
]).meta({ secret: true });
|
|
460
471
|
var gatewayChoice = object({
|
|
461
472
|
value: string(),
|
|
462
473
|
label: string(),
|
|
@@ -495,11 +506,17 @@ var homeComActions = defineCustomActions({
|
|
|
495
506
|
}),
|
|
496
507
|
finishSignIn: customAction(object({
|
|
497
508
|
attemptId: string().min(1),
|
|
498
|
-
callback: string().min(1)
|
|
509
|
+
callback: string().min(1),
|
|
510
|
+
/** An existing instance: the new sign-in is stored on it and applied at once. */
|
|
511
|
+
integrationId: string().min(1).optional()
|
|
499
512
|
}), object({
|
|
500
|
-
token:
|
|
501
|
-
refresh:
|
|
502
|
-
username:
|
|
513
|
+
token: signInSecret,
|
|
514
|
+
refresh: signInSecret,
|
|
515
|
+
username: union([
|
|
516
|
+
string(),
|
|
517
|
+
secretView,
|
|
518
|
+
_null()
|
|
519
|
+
]).meta({ secret: true }),
|
|
503
520
|
accountLabel: string(),
|
|
504
521
|
gatewayCount: number(),
|
|
505
522
|
summary: string(),
|
|
@@ -511,13 +528,18 @@ var homeComActions = defineCustomActions({
|
|
|
511
528
|
kind: "mutation",
|
|
512
529
|
auth: "admin"
|
|
513
530
|
}),
|
|
514
|
-
signOut: customAction(
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
531
|
+
signOut: customAction(
|
|
532
|
+
/** An existing instance: its stored sign-in is cleared and its connection stopped. */
|
|
533
|
+
object({ integrationId: string().min(1).optional() }),
|
|
534
|
+
object({
|
|
535
|
+
signedOut: literal(true),
|
|
536
|
+
summary: string()
|
|
537
|
+
}),
|
|
538
|
+
{
|
|
539
|
+
kind: "mutation",
|
|
540
|
+
auth: "admin"
|
|
541
|
+
}
|
|
542
|
+
),
|
|
521
543
|
/** Read-only walk of the gateway's resource tree, for diagnosing what it serves. */
|
|
522
544
|
exploreResources: customAction(exploreResourcesInput, exploreResourcesOutput, {
|
|
523
545
|
kind: "query",
|
|
@@ -525,6 +547,132 @@ var homeComActions = defineCustomActions({
|
|
|
525
547
|
})
|
|
526
548
|
});
|
|
527
549
|
//#endregion
|
|
550
|
+
//#region src/homecom/homecom-account.ts
|
|
551
|
+
/**
|
|
552
|
+
* Sign in / sign out of an EXISTING HomeCom instance, written through the instance record — the
|
|
553
|
+
* owner of the tokens (keychain-backed secret fields). The record's change hook reconciles the
|
|
554
|
+
* integration: new tokens are applied to the running manager, cleared ones stop it.
|
|
555
|
+
*/
|
|
556
|
+
/** Forget the account: the three secrets are cleared in the keychain, the account labels reset. */
|
|
557
|
+
async function signOutHomeComInstance(integrationId, instances) {
|
|
558
|
+
await instances.updateInstance({
|
|
559
|
+
id: integrationId,
|
|
560
|
+
values: {
|
|
561
|
+
token: SECRET_CLEAR,
|
|
562
|
+
refresh: SECRET_CLEAR,
|
|
563
|
+
username: SECRET_CLEAR,
|
|
564
|
+
accountLabel: "",
|
|
565
|
+
gatewayCount: 0
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Store a finished sign-in on the instance. The gateway (and its brand / region) is written only
|
|
571
|
+
* when the sign-in settled on one: a choice still open keeps the stored gateway.
|
|
572
|
+
*/
|
|
573
|
+
async function storeHomeComSignIn(integrationId, finished, instances) {
|
|
574
|
+
await instances.updateInstance({
|
|
575
|
+
id: integrationId,
|
|
576
|
+
values: {
|
|
577
|
+
token: finished.token,
|
|
578
|
+
refresh: finished.refresh,
|
|
579
|
+
username: finished.username,
|
|
580
|
+
accountLabel: finished.accountLabel,
|
|
581
|
+
gatewayCount: finished.gatewayCount,
|
|
582
|
+
...finished.gatewayId !== void 0 ? { gatewayId: finished.gatewayId } : {},
|
|
583
|
+
...finished.brand !== void 0 ? { brand: finished.brand } : {},
|
|
584
|
+
...finished.region !== void 0 ? { region: finished.region } : {}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* The answer to a sign-in on an existing instance: the tokens are on the record now, so the form
|
|
590
|
+
* gets the record's "set" views (as `hydrateSchema` hands them out) in their place — a Save that
|
|
591
|
+
* follows keeps them, and the values never travel to the browser.
|
|
592
|
+
*/
|
|
593
|
+
async function storedSignInResult(integrationId, finished, handle) {
|
|
594
|
+
const data = (await handle.get(integrationId))?.data ?? {};
|
|
595
|
+
const { token: _token, refresh: _refresh, username: _username, ...open } = finished;
|
|
596
|
+
return {
|
|
597
|
+
...open,
|
|
598
|
+
token: toSecretFieldView(data["token"]),
|
|
599
|
+
refresh: toSecretFieldView(data["refresh"]),
|
|
600
|
+
username: toSecretFieldView(data["username"])
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/homecom/homecom-reconcile.ts
|
|
605
|
+
/**
|
|
606
|
+
* Bring the running HomeCom managers in line with the enabled integrations:
|
|
607
|
+
*
|
|
608
|
+
* - signed in -> upsert (a changed connection is applied to the running manager);
|
|
609
|
+
* - signed out -> the manager stops and the devices go offline; the integration still
|
|
610
|
+
* exists, so its devices and its session alert are KEPT;
|
|
611
|
+
* - unreadable -> left as it is (a locked keychain is not a sign out, nor a deletion);
|
|
612
|
+
* - gone / disabled -> the manager stops and the caller drops what belonged to it.
|
|
613
|
+
*
|
|
614
|
+
* "Gone" is judged over EVERY id the addon still holds something for — a running manager, an
|
|
615
|
+
* instance record, a device — not only the running managers: an integration that was signed out
|
|
616
|
+
* or unreadable has no manager, and its devices and alert must still go when it is deleted.
|
|
617
|
+
*/
|
|
618
|
+
async function reconcileHomeComIntegrations(deps) {
|
|
619
|
+
const enabled = await deps.listEnabled();
|
|
620
|
+
const present = new Set(enabled.map((integration) => integration.id));
|
|
621
|
+
for (const integration of enabled) await reconcileOne(deps, integration);
|
|
622
|
+
const vanished = [...new Set([...deps.registry.list(), ...await deps.knownIds()])].filter((id) => !present.has(id));
|
|
623
|
+
await deps.registry.retain(present);
|
|
624
|
+
for (const integrationId of vanished) await deps.onVanished(integrationId);
|
|
625
|
+
}
|
|
626
|
+
async function reconcileOne(deps, integration) {
|
|
627
|
+
const tags = { integrationId: integration.id };
|
|
628
|
+
try {
|
|
629
|
+
const connection = await deps.connectionOf(integration.id);
|
|
630
|
+
deps.onNamed(integration.id, integration.name);
|
|
631
|
+
if (connection === null) {
|
|
632
|
+
if (deps.registry.has(integration.id)) {
|
|
633
|
+
await deps.registry.remove(integration.id);
|
|
634
|
+
deps.logger.info("HomeCom integration has no sign-in — stopped, devices offline", { tags });
|
|
635
|
+
}
|
|
636
|
+
deps.onSignedOut(integration.id);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
await deps.registry.upsert(integration.id, integration.name, connection);
|
|
640
|
+
} catch (err) {
|
|
641
|
+
deps.logger.warn("HomeCom reconcile failed for integration", {
|
|
642
|
+
tags,
|
|
643
|
+
meta: { error: errMsg(err) }
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
//#endregion
|
|
648
|
+
//#region src/homecom/homecom-session-alert.ts
|
|
649
|
+
/** The standard session-lost alert for HomeCom; "Sign in again" opens the integration page. */
|
|
650
|
+
function createHomeComSessionAlert(deps) {
|
|
651
|
+
const tracker = new SessionHealthTracker({
|
|
652
|
+
kind: "homecom",
|
|
653
|
+
providerName: "Bosch HomeCom",
|
|
654
|
+
logger: deps.logger,
|
|
655
|
+
getAlerts: deps.getAlerts,
|
|
656
|
+
debounceMs: deps.debounceMs ?? 0,
|
|
657
|
+
addonId: deps.addonId,
|
|
658
|
+
severity: "critical"
|
|
659
|
+
});
|
|
660
|
+
return {
|
|
661
|
+
authFailed: (integrationId) => tracker.lost(integrationId, {
|
|
662
|
+
integrationName: deps.nameOf(integrationId),
|
|
663
|
+
reason: "token-expired",
|
|
664
|
+
action: {
|
|
665
|
+
label: "Sign in again",
|
|
666
|
+
href: integrationPageHref(deps.addonId, integrationId),
|
|
667
|
+
navigation: "app"
|
|
668
|
+
}
|
|
669
|
+
}),
|
|
670
|
+
connected: (integrationId) => tracker.restored(integrationId),
|
|
671
|
+
removed: (integrationId) => tracker.forget(integrationId),
|
|
672
|
+
dispose: () => tracker.dispose()
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
//#endregion
|
|
528
676
|
//#region src/homecom/homecom-explore-action.ts
|
|
529
677
|
/** The action refused before it walked anything; `reason` says why. */
|
|
530
678
|
var HomeComExploreRefusedError = class extends Error {
|
|
@@ -1844,6 +1992,14 @@ function homeComResourceUrl(gatewayId, resourcePath) {
|
|
|
1844
1992
|
const path = resourcePath.startsWith("/") ? resourcePath : `/${resourcePath}`;
|
|
1845
1993
|
return `${HOMECOM_POINTT_API_ORIGIN}${HOMECOM_GATEWAYS_PATH}${encodeURIComponent(gatewayId)}/resource${path}`;
|
|
1846
1994
|
}
|
|
1995
|
+
async function isInvalidGrantBody(res) {
|
|
1996
|
+
try {
|
|
1997
|
+
const body = await res.json();
|
|
1998
|
+
return isRecord$1(body) && body["error"] === "invalid_grant";
|
|
1999
|
+
} catch {
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
1847
2003
|
function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
1848
2004
|
const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
1849
2005
|
const refreshOnUnauthorized = deps.refreshOnUnauthorized ?? true;
|
|
@@ -1964,6 +2120,7 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
1964
2120
|
});
|
|
1965
2121
|
if (!res.ok) {
|
|
1966
2122
|
if (res.status === 401 || res.status === 403) throw new HomeComAuthError("refresh refused");
|
|
2123
|
+
if (res.status === 400 && await isInvalidGrantBody(res)) throw new HomeComAuthError("refresh refused");
|
|
1967
2124
|
throw new Error(`HomeCom token refresh failed: HTTP ${res.status}`);
|
|
1968
2125
|
}
|
|
1969
2126
|
const body = await res.json();
|
|
@@ -2383,7 +2540,10 @@ function buildSerialPatcher(deps) {
|
|
|
2383
2540
|
const run = async () => {
|
|
2384
2541
|
try {
|
|
2385
2542
|
const stored = await deps.read(integrationId);
|
|
2386
|
-
if (stored === null)
|
|
2543
|
+
if (stored === null) {
|
|
2544
|
+
deps.logger.warn(`HomeCom not persisting ${deps.what} — the integration record is gone`, { tags: { integrationId } });
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2387
2547
|
if (stored.locked) {
|
|
2388
2548
|
deps.logger.warn(`HomeCom not persisting ${deps.what} — stored secrets are locked`, { tags: { integrationId } });
|
|
2389
2549
|
return;
|
|
@@ -2797,6 +2957,7 @@ var HomeComIntegrationManager = class {
|
|
|
2797
2957
|
#makeClient;
|
|
2798
2958
|
#pollIntervalMs;
|
|
2799
2959
|
#onGatewayChosen;
|
|
2960
|
+
#onAuthFailed;
|
|
2800
2961
|
#gateways = [];
|
|
2801
2962
|
#client = null;
|
|
2802
2963
|
#gatewayId = null;
|
|
@@ -2815,6 +2976,7 @@ var HomeComIntegrationManager = class {
|
|
|
2815
2976
|
const fetchFn = options.fetchFn;
|
|
2816
2977
|
const bridge = options.bridge;
|
|
2817
2978
|
this.#onGatewayChosen = options.onGatewayChosen ?? (() => void 0);
|
|
2979
|
+
this.#onAuthFailed = options.onAuthFailed ?? (() => void 0);
|
|
2818
2980
|
const onTokensRotated = async (tokens) => {
|
|
2819
2981
|
this.#connection = {
|
|
2820
2982
|
...this.#connection,
|
|
@@ -2883,7 +3045,9 @@ var HomeComIntegrationManager = class {
|
|
|
2883
3045
|
}, this.#pollIntervalMs);
|
|
2884
3046
|
} catch (err) {
|
|
2885
3047
|
this.#status = "error";
|
|
2886
|
-
|
|
3048
|
+
const authFailed = err instanceof HomeComAuthError;
|
|
3049
|
+
this.#error = authFailed ? "authentication failed — sign in again" : errMsg(err);
|
|
3050
|
+
if (authFailed) this.#onAuthFailed(this.#id);
|
|
2887
3051
|
this.#client = null;
|
|
2888
3052
|
this.#surfaceSink.set(this.#id, null);
|
|
2889
3053
|
this.#onDisconnected(this.#id);
|
|
@@ -2915,6 +3079,7 @@ var HomeComIntegrationManager = class {
|
|
|
2915
3079
|
this.#surfaceSink.updateSnapshot(this.#id, snapshot);
|
|
2916
3080
|
this.#lastCheckedAt = Date.now();
|
|
2917
3081
|
} catch (err) {
|
|
3082
|
+
if (err instanceof HomeComAuthError) this.#onAuthFailed(this.#id);
|
|
2918
3083
|
this.#logger.warn("HomeCom poll failed", { meta: {
|
|
2919
3084
|
integrationId: this.#id,
|
|
2920
3085
|
error: errMsg(err)
|
|
@@ -2949,6 +3114,7 @@ var HomeComClientRegistry = class {
|
|
|
2949
3114
|
#makeManager;
|
|
2950
3115
|
#onTokensRotated;
|
|
2951
3116
|
#onGatewayChosen;
|
|
3117
|
+
#onAuthFailed;
|
|
2952
3118
|
#entries = /* @__PURE__ */ new Map();
|
|
2953
3119
|
constructor(logger, deps = {}) {
|
|
2954
3120
|
this.#logger = logger;
|
|
@@ -2956,6 +3122,7 @@ var HomeComClientRegistry = class {
|
|
|
2956
3122
|
this.#onDisconnected = deps.onDisconnected ?? (() => void 0);
|
|
2957
3123
|
this.#onTokensRotated = deps.onTokensRotated ?? (async () => void 0);
|
|
2958
3124
|
this.#onGatewayChosen = deps.onGatewayChosen ?? (() => void 0);
|
|
3125
|
+
this.#onAuthFailed = deps.onAuthFailed ?? (() => void 0);
|
|
2959
3126
|
const fetchFn = deps.fetchFn;
|
|
2960
3127
|
const makeClient = deps.makeClient;
|
|
2961
3128
|
const bridge = deps.bridge;
|
|
@@ -2968,6 +3135,7 @@ var HomeComClientRegistry = class {
|
|
|
2968
3135
|
onDisconnected: opts.onDisconnected,
|
|
2969
3136
|
onTokensRotated: opts.onTokensRotated,
|
|
2970
3137
|
onGatewayChosen: opts.onGatewayChosen,
|
|
3138
|
+
onAuthFailed: opts.onAuthFailed,
|
|
2971
3139
|
surfaceSink: homeComConnections,
|
|
2972
3140
|
fetchFn,
|
|
2973
3141
|
makeClient,
|
|
@@ -3007,7 +3175,8 @@ var HomeComClientRegistry = class {
|
|
|
3007
3175
|
onConnected: (id) => this.#onConnected(id),
|
|
3008
3176
|
onDisconnected: (id) => this.#onDisconnected(id),
|
|
3009
3177
|
onTokensRotated: (tokens) => this.#onTokensRotated(integrationId, tokens),
|
|
3010
|
-
onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId)
|
|
3178
|
+
onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId),
|
|
3179
|
+
onAuthFailed: (id) => this.#onAuthFailed(id)
|
|
3011
3180
|
});
|
|
3012
3181
|
this.#entries.set(integrationId, {
|
|
3013
3182
|
manager,
|
|
@@ -3659,6 +3828,8 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
3659
3828
|
};
|
|
3660
3829
|
clients = null;
|
|
3661
3830
|
instances = null;
|
|
3831
|
+
sessionAlert = null;
|
|
3832
|
+
integrationNames = /* @__PURE__ */ new Map();
|
|
3662
3833
|
constructor() {
|
|
3663
3834
|
super({});
|
|
3664
3835
|
}
|
|
@@ -3685,12 +3856,21 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
3685
3856
|
const persistTokens = buildHomeComTokenPersister(persistDeps);
|
|
3686
3857
|
const persistGateway = buildHomeComGatewayPersister(persistDeps);
|
|
3687
3858
|
const bridge = await this.createBridge();
|
|
3859
|
+
const sessionAlert = createHomeComSessionAlert({
|
|
3860
|
+
logger: this.ctx.logger,
|
|
3861
|
+
getAlerts: () => this.ctx.api.alerts,
|
|
3862
|
+
nameOf: (id) => this.integrationNames.get(id) ?? id,
|
|
3863
|
+
addonId: this.addonId
|
|
3864
|
+
});
|
|
3865
|
+
this.sessionAlert = sessionAlert;
|
|
3688
3866
|
this.clients = new HomeComClientRegistry(this.ctx.logger, {
|
|
3867
|
+
onAuthFailed: (integrationId) => sessionAlert.authFailed(integrationId),
|
|
3689
3868
|
...bridge !== null ? { bridge } : {},
|
|
3690
3869
|
onTokensRotated: (integrationId, tokens) => persistTokens(integrationId, tokens),
|
|
3691
3870
|
onGatewayChosen: (integrationId, gatewayId) => void persistGateway(integrationId, gatewayId)
|
|
3692
3871
|
});
|
|
3693
3872
|
this.clients.setOnConnected((integrationId) => {
|
|
3873
|
+
sessionAlert.connected(integrationId);
|
|
3694
3874
|
this.setIntegrationDevicesOnline(integrationId, true);
|
|
3695
3875
|
});
|
|
3696
3876
|
this.clients.setOnDisconnected((integrationId) => {
|
|
@@ -3721,15 +3901,30 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
3721
3901
|
...publishHomeComSignIn(homeComSignInAttempts, Date.now()),
|
|
3722
3902
|
summary: "Open the sign-in page, sign in, then paste the address the browser stops on."
|
|
3723
3903
|
}),
|
|
3724
|
-
finishSignIn: async (input) =>
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3904
|
+
finishSignIn: async (input) => {
|
|
3905
|
+
const finished = await finishHomeComSignIn({
|
|
3906
|
+
attemptId: input.attemptId,
|
|
3907
|
+
callback: input.callback,
|
|
3908
|
+
nowMs: Date.now()
|
|
3909
|
+
}, homeComSignInAttempts);
|
|
3910
|
+
const integrationId = input.integrationId;
|
|
3911
|
+
if (integrationId === void 0) return finished;
|
|
3912
|
+
await this.storeSignIn(integrationId, () => storeHomeComSignIn(integrationId, finished, built.instances));
|
|
3913
|
+
return storedSignInResult(integrationId, finished, built.handle);
|
|
3914
|
+
},
|
|
3915
|
+
signOut: async (input) => {
|
|
3916
|
+
const integrationId = input.integrationId;
|
|
3917
|
+
if (integrationId === void 0) return {
|
|
3918
|
+
signedOut: true,
|
|
3919
|
+
summary: "Signed out."
|
|
3920
|
+
};
|
|
3921
|
+
await this.storeSignIn(integrationId, () => signOutHomeComInstance(integrationId, built.instances));
|
|
3922
|
+
this.ctx.logger.info("HomeCom signed out — stored sign-in cleared", { tags: { integrationId } });
|
|
3923
|
+
return {
|
|
3924
|
+
signedOut: true,
|
|
3925
|
+
summary: "Signed out. The devices stay offline until you sign in again."
|
|
3926
|
+
};
|
|
3927
|
+
},
|
|
3733
3928
|
exploreResources: explore
|
|
3734
3929
|
}
|
|
3735
3930
|
};
|
|
@@ -3742,6 +3937,8 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
3742
3937
|
}
|
|
3743
3938
|
this.clients = null;
|
|
3744
3939
|
this.instances = null;
|
|
3940
|
+
this.sessionAlert?.dispose();
|
|
3941
|
+
this.sessionAlert = null;
|
|
3745
3942
|
await super.onShutdown();
|
|
3746
3943
|
}
|
|
3747
3944
|
/** The `homecom_alt` bridge on the embedded Python; null leaves k40/k30/icom on plain reads. */
|
|
@@ -3772,38 +3969,60 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
|
|
|
3772
3969
|
if (typeof nativeId !== "string" || nativeId.length === 0) throw new Error("homecom generateStableId: nativeId required");
|
|
3773
3970
|
return homeComStableId(integrationId, nativeId);
|
|
3774
3971
|
}
|
|
3972
|
+
/** Write a sign-in change on an instance that exists; a missing one is refused by name. */
|
|
3973
|
+
async storeSignIn(integrationId, write) {
|
|
3974
|
+
if (await homeComStoredOf(this.requireInstances().instances, integrationId) === null) throw new Error(`HomeCom integration ${integrationId} does not exist`);
|
|
3975
|
+
await write();
|
|
3976
|
+
}
|
|
3775
3977
|
async reconcileIntegrations() {
|
|
3776
3978
|
const reg = this.requireClients();
|
|
3777
3979
|
try {
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
this.
|
|
3787
|
-
|
|
3788
|
-
error: errMsg(err)
|
|
3789
|
-
} });
|
|
3790
|
-
}
|
|
3791
|
-
const vanished = reg.list().filter((id) => !surviving.has(id));
|
|
3792
|
-
await reg.retain(surviving);
|
|
3793
|
-
const deviceReg = this.ctx.kernel.deviceRegistry;
|
|
3794
|
-
const deviceApi = this.ctx.kernel.devices;
|
|
3795
|
-
if (deviceReg && deviceApi) for (const integrationId of vanished) await cascadeRemoveDevicesForIntegration({
|
|
3796
|
-
reg: deviceReg,
|
|
3797
|
-
devices: deviceApi,
|
|
3798
|
-
addonId: this.addonId,
|
|
3799
|
-
system: "homecom",
|
|
3800
|
-
integrationId,
|
|
3980
|
+
await reconcileHomeComIntegrations({
|
|
3981
|
+
listEnabled: async () => {
|
|
3982
|
+
return (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id && i.enabled);
|
|
3983
|
+
},
|
|
3984
|
+
knownIds: () => this.knownIntegrationIds(),
|
|
3985
|
+
connectionOf: (id) => homeComConnectionOf(this.requireInstances().instances, id),
|
|
3986
|
+
registry: reg,
|
|
3987
|
+
onNamed: (id, name) => this.integrationNames.set(id, name),
|
|
3988
|
+
onSignedOut: (id) => this.setIntegrationDevicesOnline(id, false),
|
|
3989
|
+
onVanished: (id) => this.dropIntegration(id),
|
|
3801
3990
|
logger: this.ctx.logger
|
|
3802
3991
|
});
|
|
3803
3992
|
} catch (err) {
|
|
3804
3993
|
this.ctx.logger.warn("HomeCom integration reconcile failed", { meta: { error: errMsg(err) } });
|
|
3805
3994
|
}
|
|
3806
3995
|
}
|
|
3996
|
+
/** Every integration this addon holds a record or a top-level device for. */
|
|
3997
|
+
async knownIntegrationIds() {
|
|
3998
|
+
const ids = new Set(this.integrationNames.keys());
|
|
3999
|
+
for (const row of await this.requireInstances().instances.listResolved()) ids.add(row.id);
|
|
4000
|
+
const reg = this.ctx.kernel.deviceRegistry;
|
|
4001
|
+
const devices = this.ctx.kernel.devices;
|
|
4002
|
+
if (reg && devices) for (const d of reg.getAllForAddon(this.addonId)) {
|
|
4003
|
+
if (d.parentDeviceId !== null) continue;
|
|
4004
|
+
const cfg = await devices.loadConfig(d.id).catch(() => ({}));
|
|
4005
|
+
const brokerId = cfg["brokerId"];
|
|
4006
|
+
if (cfg["system"] === "homecom" && typeof brokerId === "string") ids.add(brokerId);
|
|
4007
|
+
}
|
|
4008
|
+
return [...ids];
|
|
4009
|
+
}
|
|
4010
|
+
/** The integration is gone: its alert and its devices go with it. */
|
|
4011
|
+
async dropIntegration(integrationId) {
|
|
4012
|
+
this.integrationNames.delete(integrationId);
|
|
4013
|
+
this.sessionAlert?.removed(integrationId);
|
|
4014
|
+
const deviceReg = this.ctx.kernel.deviceRegistry;
|
|
4015
|
+
const deviceApi = this.ctx.kernel.devices;
|
|
4016
|
+
if (!deviceReg || !deviceApi) return;
|
|
4017
|
+
await cascadeRemoveDevicesForIntegration({
|
|
4018
|
+
reg: deviceReg,
|
|
4019
|
+
devices: deviceApi,
|
|
4020
|
+
addonId: this.addonId,
|
|
4021
|
+
system: "homecom",
|
|
4022
|
+
integrationId,
|
|
4023
|
+
logger: this.ctx.logger
|
|
4024
|
+
});
|
|
4025
|
+
}
|
|
3807
4026
|
subscribeIntegrationLifecycle() {
|
|
3808
4027
|
const handler = (event) => {
|
|
3809
4028
|
const addonId = event.data["addonId"];
|
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-
|
|
5
|
+
const require_bosch_instances = require("../bosch-instances-BnuV5On6.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");
|
package/dist/shc/addon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as literal, A as registerPowerSwitch, B as defineCustomActions, H as entryBody, I as RecordIntegrationInstances, J as _enum, K as rowsDepsOver, N as switchCapability, P as temperatureSensorCapability, S as heatingValveCapability, V as defineEntryKind, X as array, a as BaseDevice, ct as errMsg, et as number, f as climateControlCapability, h as deviceAdoptionCapability, i as testBoschInstance, lt as EventCategory, m as defaultIntegrationName, n as boschRowsPorts, o as BaseDeviceProvider, ot as DeviceRole, r as keepBodyKeys, rt as string, st as DeviceType, t as boschRowsMigration, tt as object, u as batteryCapability, w as integrationInstancesCapability, y as gatewayDeviceName, z as customAction } from "../bosch-instances-REcWLM14.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.
|
|
3
|
+
"version": "0.1.56",
|
|
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",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
}
|
|
143
143
|
],
|
|
144
144
|
"requires": {
|
|
145
|
-
"server": ">=1.2.
|
|
145
|
+
"server": ">=1.2.882"
|
|
146
146
|
}
|
|
147
147
|
},
|
|
148
148
|
"files": [
|